한국어 · English · 日本語 · 中文 · Français · Español
공동작업이나 기여를 해 주셨거나 해 주실 분은 개인적으로 연락 부탁드립니다. 이 프로젝트는 누구나 무료로 쓸 수 있도록 소스를 공개한 소프트웨어입니다. 개인적 · 비영리 목적이라면 비용 없이 그대로 사용할 수 있습니다. 다만 저작권과 소유권은 원작자와 공동작업자와 기여자에게 있어, 원작자의 허락 없이 수정하거나 그 일부를 떼어 자기 프로그램에 넣어 쓸 수 없고(본인만 쓰는 경우도 포함), 이 프로그램을 자기 것으로 주장하거나 다른 곳에 배포 · 판매할 수 없습니다. GitHub 포크에 수정한 내용을 올리는 것도 허용하지 않습니다. 학교, 학원, 동아리, 교회, 종교 단체, 회사 같은 단체에서 쓰는 것은 무조건 무료가 아니니 먼저 연락 부탁드립니다. 자세한 조건은 LICENSE에 있습니다.
Chordity는 노래 파일을 넣으면 마디마다 코드를 적어 주고, 노래의 박에 딱 맞춰 메트로놈을 쳐 주는 웹 페이지입니다.
코드가 바뀌는 순간에는 피아노가 그 코드를 쳐 줘서, 귀로 확인하며 따라 칠 수 있습니다. 설치도, 회원 가입도, 인터넷 연결도 필요 없습니다. 음악 파일은 내 컴퓨터 밖으로 나가지 않습니다.
결과는 틀릴 수 있습니다. 분석 원리와 판단의 근거, 결과를 확인하고 바로잡는 방법은 도움말 페이지에 정리해 두었습니다. 앱 맨 아래의 안내 문구를 눌러도 열립니다.
Chordity: per-measure chord recognition, a beat-locked metronome and synthesized piano chord playback in a single offline HTML file. No libraries, no uploads.

chordity.html 파일을 크롬, 엣지, 사파리, 파이어폭스 같은 브라우저로 엽니다.MP3, WAV, M4A, FLAC, OGG처럼 브라우저가 재생할 수 있는 파일이면 됩니다.
| 보이는 것 | 뜻 |
|---|---|
| 노란 형광펜 상자 | 지금 울리는 코드. 그 코드를 친 박부터 다음 코드가 나오는 곳까지 감쌈(끌어 두는 회색 코드가 다음 마디로 이어지면 거기까지, 줄이 바뀌면 줄마다 한 조각). 코드가 바뀌면 용수철처럼 튕기며 다음 코드로 옮겨 감 |
| 상자 안 주황 채움 | 그 코드에서 지금까지 연주된 만큼(진행 막대). 채움 끝이 지금 재생 위치이고, 박이 시작할 때 그 박의 빗금 자리에 닿음 |
| 커진 코드 이름 | 지금 울리는 코드의 이름(새로 친 코드). 들어올 때 확 커졌다가 다음 코드로 넘어가면 천천히 제 크기로 줄어듦. 다음 이름에 닿지 않을 만큼만 커짐 |
빗금 / |
한 박. 진하게 바뀌는 빗금이 지금 박 |
| 두꺼운 빗금 | 코드를 새로 치는 박(검은 글자 코드가 놓인 박). 어느 리듬에서 코드를 치는지 보임. 회색 코드와 N.C. 자리는 보통 빗금 |
C Am G7 |
코드 이름. 빗금 바로 위에 있으면 그 박에서 코드가 바뀜 |
G/B |
G 코드인데 가장 낮은 음이 B |
| 작은 글씨, 위·아래 두 줄로 놓인 코드 이름 | 한 마디에 코드가 많아 이름이 다음 코드에 닿으면 글씨를 줄이고, 그래도 안 들어가면 그 마디의 이름을 위·아래 두 줄로 번갈아 놓음(이름의 왼쪽 끝이 그 코드를 치는 박). 좁은 화면에서 마디 끝 박의 긴 이름이 두 줄로도 안 들어가면 덜 중요한 부분부터 빼고 …을 붙임(Bm7/F♯ → Bm7…). 전체 이름은 마우스를 올리거나 재생 중 위쪽 지금 코드 칸에서 보임 |
| 회색 코드 | 앞 마디의 코드를 새로 치지 않고 그대로 끌어 둠. 같은 코드라도 마디 첫 박에 새로 치면 검은 글자 |
N.C. |
코드가 없는 부분 (드럼만 나오거나 조용할 때) |
| 0번 마디 | 노래가 마디 중간에서 시작하는 부분 (못갖춘마디) |
마디 위의 2박 같은 표시 |
그 마디만 박 수가 다름 |
검은 이름표 E 장조로 |
그 박부터 조가 바뀜(전조). 위쪽 조성 칸에도 E♭ 장조 → E 장조처럼 나옴. 곡 첫 마디에는 시작 조성과 박자가 조사 없이 붙음(C 장조 4/4, 악보의 조표·박자표처럼) |
검은 이름표 3/4으로 |
그 마디부터 박자가 바뀜(박자 변화). 위쪽 박자 칸에도 4/4 → 3/4 → 4/4처럼 나옴 |
흰 알약 72 132로 |
그 박부터의 템포(BPM, 반올림), 슬래시 아래 줄. 곡이 시작하는 박에는 시작 템포를 숫자만, 템포가 갑자기 바뀐 곳에는 새 템포에 로/으로를 붙임 |
흰 육각형 틀 여기까지 63 |
템포가 서서히 바뀌는 구간(슬래시 아래 줄). 틀이 걸친 길이가 바뀌는 구간이고, 재생 위치가 틀 안에 있으면 지나온 만큼 형광펜 색으로 차면서 현재 67처럼 지금 템포를 보여 줌. 끝까지 차면 흰색으로 옅어지고, 틀 끝 다음 박의 흰 알약(도착한 템포)이 잠깐 켜짐 |
| 템포(위쪽 칸과 재생 막대) | 재생하는 자리의 템포. 템포가 서서히 바뀌면 120, 119, 118 …처럼 따라 움직임. 재생 막대는 스크롤해도 화면 맨 위에 고정돼 늘 보임 |
8B, 9A에 작은 글자가 붙은 이름 |
카멜롯 표기를 골랐을 때의 코드 이름(아래 “카멜롯으로 보기” 참고) |
I, vi, V7/ii 같은 로마 숫자 |
로마 숫자 표기를 골랐을 때의 코드 이름(아래 “로마 숫자로 보기” 참고) |
| 동그라미 표시등 | 지금 몇 번째 박인지. 가장 큰 동그라미가 마디의 첫 박 |
| 위쪽의 “다음 G” | 곧 나올 코드 |
| 메트로놈, 코드 연주 버튼 | 누를 때마다 켜고 끔 |
| 음악, 클릭, 코드 막대 | 원곡, 메트로놈, 피아노 코드 소리의 크기를 따로 조절 |
대부분 알아서 맞춰지지만, 노래에 따라 사람마다 박을 다르게 세기도 합니다. 박이 안 맞나요? 보정하기를 열고 아래 버튼을 눌러 보세요. 누르는 즉시 바뀝니다.
| 이렇게 들리면 | 이 버튼을 |
|---|---|
| 클릭이 박과 박 사이에 들린다 | ½박 옮기기 |
| 클릭이 너무 촘촘하다 | 절반으로 ÷2 |
| 클릭이 너무 드문드문하다 | 두 배 빠르게 ×2 |
| 6/8 느낌의 곡인데 클릭이 너무 많다 | 셋씩 묶기 ÷3 |
| 마디가 시작되는 곳이 한 박 어긋났다 | 한 박 앞당기기, 한 박 늦추기 |
| 클릭이 아주 살짝 이르거나 늦게 느껴진다 | 클릭 타이밍 |
| 박 보정을 처음 상태로 돌리고 싶다 | 박 보정 되돌리기 |
| 코드 종류 같은 설정을 처음 상태로 돌리고 싶다 | 설정 기본값으로 |
지금 무엇을 바꿔 두었는지는 같은 칸의 적용 중에 “박 보정”과 “설정”으로 나뉘어 나옵니다. 칸을 접어 두어도 제목 옆에 요약이 보입니다.
코드를 더 간단하게 보고 싶으면 코드 종류에서 “기본”을, 한 박 안에서 바뀌는 코드까지 보고 싶으면 “½박 단위”를 고르세요. ½박 단위에서는 메트로놈도 박 사이(8분음표 자리)에 작고 높은 “틱”을 넣어 “띡-틱-딱-틱-딱-틱-딱-틱”처럼 칩니다.
차트 바로 위의 코드 표기 막대에서 같은 코드를 세 가지 방식으로 볼 수 있습니다. 누르는 즉시 차트, 지금·다음 코드, 텍스트 차트가 함께 바뀌고, 고른 방식은 다음에 열어도 그대로입니다. 키보드로는 막대에 초점을 둔 채 ←, →로 바꿉니다.
| 버튼 | 예 | 이럴 때 좋아요 |
|---|---|---|
| 음이름 | E♭, B♭/D, Cm7 | 코드를 보며 바로 따라 치거나, 연주자끼리 코드를 주고받을 때 |
| 카멜롯 | 5B, 6B, 3A⁷ | 잘 어울리는 조성과 코드를 찾거나, DJ 믹스 지점을 고를 때 |
| 로마 숫자 | I, V⁶, ii⁷/IV | 곡의 화성 진행을 분석하거나, 다른 조로 옮겨 칠 때 |
아래 세 그림은 E♭ 장조로 된 같은 11마디를 각 방식으로 본 모습입니다.

가장 익숙한 코드 이름입니다. 근음은 크게, m·sus·dim 같은 성질은 조금 작게, 7·maj7 같은 확장음은 위첨자로 적어 복잡한 코드도 한눈에 읽힙니다.
B♭/D는 B♭ 코드를 치되 가장 낮은 음을 D로 두라는 뜻입니다. 여섯째 마디의 B♭7/A♭처럼 7음을 베이스로 둔 코드도 그대로 적습니다.N.C.로 적습니다. 같은 코드라도 마디 첫 박에 새로 치면 검은 글자로 적습니다.
DJ가 곡과 곡을 이어 붙일 때 쓰는 카멜롯 휠 번호로 적습니다. 조성을 5도권 순서대로 1~12번 자리에 놓고, 장조 계열은 B, 단조 계열은 A를 붙입니다. 번호가 같거나 하나 차이 나는 조성과 코드끼리는 소리가 잘 어울립니다.
3A⁷(B♭m7)만 번호가 멀리 떨어져 있는데, IV인 A♭maj7(4B)로 가기 위해 잠시 빌려 온 화음이기 때문입니다.E♭ 장조(5B)처럼 번호와 함께 보여 줍니다.B♭/D → 6B). 코드 연주는 베이스음까지 그대로 칩니다.
화성학 책에서 쓰는 로마 숫자 화성 분석입니다. 코드를 음이름 대신 조성 안에서 몇 번째 음 위의 화음인지로 적어서, 곡이 어떤 뼈대로 움직이는지 드러냅니다. 조가 달라도 같은 진행은 같은 숫자로 보이므로 다른 조로 옮겨 치거나 여러 곡의 진행을 비교하기 좋습니다.
I – V⁶ – vi는 베이스가 E♭ → D → C로 한 음씩 내려가는 진행입니다. 이어지는 ii⁷/IV – V⁷/IV – IVᴹ⁷은 IV(A♭maj7)로 가기 위해 A♭ 장조의 ii–V를 잠시 빌려 오는 진행이고, 마지막 ii⁷ – V⁷ – I은 가장 기본적인 마침(종지)입니다.V⁶은 3음(D)을, 여섯째 마디의 V⁴₂는 7음(A♭)을 베이스로 둔 화음입니다.| 표기 | 뜻 |
|---|---|
| I, IV, V (대문자) | 장3화음 계열 코드. 숫자는 조성의 몇 번째 음 위의 화음인지 |
| i, ii, vi (소문자) | 단3화음 계열 코드 |
| °, ø, +, M | 감화음(°), 반감7화음(ø), 증3화음(+), 장7화음(M7) |
| 7 | 7화음 |
| 6, ⁶₄ | 3화음의 자리바꿈. 베이스가 3음이면 6, 5음이면 ⁶₄ |
| ⁶₅, ⁴₃, ⁴₂ | 7화음의 자리바꿈. 베이스가 3음, 5음, 7음일 때 |
| V/x, vii°/x, ii/x | x로 가기 위해 잠시 쓰는 화음(부속화음). V7/vi는 vi로 가는 딸림7화음 |
| ♭VI, ♭VII 등 | 조성 음계 밖의 도수. 같은 으뜸음의 단조에서 빌려 온 화음(차용 화음) 등 |
코드 연주를 켜 두면 코드가 바뀌는 순간 피아노가 그 코드를 쳐 줍니다. 원곡과 겹쳐 들으면 적힌 코드가 맞는지 귀로 바로 확인할 수 있고, 따라 칠 때 길잡이도 됩니다. 피아노는 곡의 음높이에 저절로 맞춰지므로 원곡과 음이 어긋나지 않습니다.
| 키 | 하는 일 |
|---|---|
| Space | 재생, 일시정지 |
| M | 메트로놈 켜기, 끄기 |
| P | 코드 연주 켜기, 끄기 |
| ← → | 이전 마디, 다음 마디 |
| Home | 처음으로 |
마디를 누르면 그 마디로 가고, 위쪽 파형을 누르거나 끌어도 원하는 곳으로 갑니다. 한글 입력 상태에서도 단축키가 동작합니다.
드럼이 있는 팝, 록, 발라드, 댄스, 힙합, 케이팝, 어쿠스틱 팝처럼 박이 분명한 노래는 잘 됩니다. 곡 중간에 템포가 크게 바뀌는 노래, 5/4·7/4 같은 변박, F/G처럼 베이스가 코드 밖의 음인 코드, 록의 파워 코드(E5)도 알아봅니다. 재즈처럼 텐션과 워킹 베이스가 많은 노래, 보사노바처럼 두 박 단위로 느끼는 노래(마디가 두 배 길이로 적힘), 디스토션이 아주 강한 메탈, 레게처럼 박을 두 가지로 셀 수 있는 노래는 틀릴 수 있습니다. 이럴 때는 위의 보정 버튼을 쓰세요.

이 프로그램은 미리 학습된 인공지능 모델을 쓰지 않습니다. 소리의 물리적 성질과 음악의 규칙을 수식으로 옮긴 신호 처리와 확률 모델로 동작합니다. 그래서 판단 근거를 하나하나 설명할 수 있고, 화면 아래 분석 정보에서 그 근거를 직접 볼 수 있습니다.
소리는 공기의 떨림이고, 녹음 파일은 그 떨림을 1초에 수만 번 잰 숫자의 나열입니다. 숫자만 봐서는 어떤 음이 울리는지 알기 어렵기 때문에, 소리를 아주 짧은 조각으로 잘라 조각마다 어떤 높이의 소리가 얼마나 섞여 있는지(스펙트럼)를 계산합니다. 이것을 시간 순으로 늘어놓으면 가로가 시간, 세로가 음높이인 소리의 그림(스펙트로그램)이 됩니다. 이후의 분석은 모두 이 그림을 읽는 일입니다.
여기에는 피할 수 없는 맞바꿈이 있습니다. 조각을 짧게 자르면 언제 소리가 났는지는 정확하지만 무슨 음인지는 흐려지고, 길게 자르면 그 반대입니다. 그래서 박을 찾을 때는 짧은 조각을, 코드를 찾을 때는 긴 조각을 따로 씁니다. 가장 낮은 베이스 음은 반음 사이의 주파수 차이가 아주 작아서 가장 긴 조각이 필요합니다.
드럼을 치거나 건반을 누르면 그 순간 소리 에너지가 갑자기 늘어납니다. 이런 순간을 온셋이라고 합니다. 스펙트로그램에서 직전보다 커진 부분만 더하면 온셋마다 뾰족하게 솟는 곡선이 생깁니다.
박은 이 온셋들이 일정한 간격으로 반복되는 뼈대입니다. 먼저 곡선을 시간 방향으로 밀어 가며 자기 자신과 겹쳐 보고(자기상관), 가장 잘 겹치는 간격, 즉 템포를 찾습니다. 그다음 “강한 온셋은 최대한 많이 지나가면서 간격은 템포에서 크게 벗어나지 않는” 박의 줄을 곡 전체에 걸쳐 한 번에 찾습니다(동적 계획법). 박을 하나씩 따로 정하지 않고 전체를 한꺼번에 최적화하기 때문에 약한 박이 몇 개 섞여 있어도 줄이 끊기지 않고, 연주 템포가 조금씩 흔들려도 따라갑니다. 이때 “일정한 간격”의 기준은 곡 전체의 템포가 아니라 몇 초 단위로 구한 국소 템포입니다. 그래서 필인처럼 잠깐 리듬이 복잡해지는 곳에는 끌려가지 않으면서, 곡이 실제로 느려지거나 빨라지면 따라갑니다.
규칙적인 줄을 찾았다고 끝이 아닙니다. 흔한 실수가 두 가지 있습니다.
엇박을 박으로 잡는 경우. 하이햇이 박 사이를 강하게 채우면 줄이 반 박 밀려 잡힐 수 있습니다. 그런데 음악에는 규칙이 있습니다. 킥, 스네어, 베이스가 들어오는 순간과 코드가 바뀌는 순간은 대부분 정박에 몰립니다. 그래서 찾은 박 위치와 반 박 옮긴 위치에서 이런 사건들의 세기를 비교하고, 반 박 옮긴 쪽이 분명히 강하면 줄 전체를 반 박 옮깁니다.
템포를 두 배나 절반으로 잡는 경우. 느린 발라드에 8분음표 아르페지오가 촘촘하면 8분음표 하나하나를 박으로 착각하기 쉽습니다. 이때는 박을 하나씩 걸러 두 무리로 나눠 봅니다. 진짜 박에는 킥과 스네어가 오고 사이에는 건반 소리만 있다면, 한 무리가 다른 무리보다 훨씬 강하게 나옵니다. 그러면 약한 무리를 버리고 템포를 절반으로 고칩니다. 반대로 박 사이 한가운데마다 스네어가 규칙적으로 오면 템포를 너무 느리게 잡은 것이므로 두 배로 고칩니다. 두 배로 늘리는 쪽은 틀리면 곧바로 엇박 클릭이 생기므로 훨씬 엄격한 조건에서만 합니다.
실제 녹음에서는 하이햇과 건반이 엇박을 채워서 두 무리의 세기 차이가 작게 나올 수 있습니다. 그래서 킥과 스네어가 어느 무리에 오는지도 봅니다. 제 템포라면 킥(1·3박)과 스네어(2·4박)가 서로 다른 무리에 오고, 두 배로 잡혔다면 둘 다 같은 무리(진짜 박)에 옵니다. 템포를 절반으로 고칠 때는 비트를 하나씩 걸러 내지 않고 새 템포로 처음부터 다시 추적합니다. 8분음표 단계의 추적이 곡 중간 어딘가에서 한 칸만 미끄러져도, 걸러 낸 비트는 그 뒤로 전부 반 박씩 어긋나기 때문입니다.
빠른 곡을 절반으로 잡는 두 가지 모양. 160~180 BPM쯤 되는 빠른 곡은 박을 한 층위 느리게, 곧 절반 템포로 잡기 쉽습니다. 이때 느린 박이 어디에 앉았는지에 따라 모양이 다릅니다. 느린 박이 스네어(2·4박)에 앉으면 박 위마다 스네어가 고르게 오고, 킥은 박 사이에 옵니다. 제 템포의 느린 곡이라면 킥이 박 위(1·3박)에 오고 스네어는 한 박 건너 오므로 나오지 않는 모양입니다. 느린 박이 킥에 앉으면(하프타임 느낌) 박 사이에도 스네어가 박 위만큼 옵니다. 두 경우 모두 템포 추정이 두 배 템포를 거의 같은 세기의 후보로 낼 때만 두 배로 고칩니다. 느린 박이 스네어에 앉은 채로 두면 마디 첫 박도 어긋나서, 한 박 안에 두 코드가 섞여 7화음과 슬래시 코드가 많이 나옵니다.
절반으로 줄이는 쪽도 한 번 더 확인합니다. 8분음표를 박으로 잡았다면 박 사이 한가운데는 16분음표 자리라 킥이나 베이스 같은 저음 타격이 드뭅니다. 그래서 박 사이에도 저음이 자주 오면 박이 제 박인 것으로 보고 줄이지 않습니다. 짝·홀 무리의 세기 차이만으로 줄이려 할 때 스네어가 킥의 반대쪽 무리에 몰려 있으면(백비트) 역시 제 박이므로 줄이지 않습니다.
“스네어”를 알아보는 방법이 물리적입니다. 스네어 밑면의 줄이 떨리면 중간 높이부터 아주 높은 소리까지 한꺼번에 커지는 잡음이 납니다. 건반의 음은 몇몇 높이에서만 커지고, 하이햇은 높은 쪽에서만 커집니다. 그래서 “중간 높이와 높은 쪽이 동시에 커졌는가”를 두 값의 곱으로 재면 스네어만 골라낼 수 있습니다. 둘 중 하나라도 작으면 곱이 작아지기 때문입니다.
박을 몇 개씩 묶어 마디를 만들려면 마디의 첫 박이 어디인지 알아야 합니다. 첫 박에는 흔적이 남습니다. 코드가 바뀌고, 베이스 음이 바뀌고, 킥이 세게 들어가고, 소리가 커지기도 합니다. 박마다 이런 흔적에 점수를 매기고, “마디는 보통 같은 길이로 반복된다”는 규칙을 지키면서 점수가 가장 높은 묶음을 찾습니다. 4박과 3박 가운데 어느 쪽이 흔적과 더 잘 맞는지도 이 방법으로 정합니다. 한 박 안이 셋으로 쪼개지는 느낌이 강하면 6/8, 12/8 같은 겹박자로 봅니다.
드럼이 백비트를 치는 곡에서는 킥은 1·3박, 스네어는 2·4박이라는 짝·홀 모양도 첫 박의 흔적으로 씁니다. 코드가 마디 첫 박보다 8분음표 먼저 바뀌는 당김 리프에서는 코드 바뀜의 흔적이 한 박 앞으로 끌려가는데, 드럼의 짝·홀 모양은 그대로이므로 마디가 한 박 밀리지 않습니다. 또 3·5·7박은 첫 박의 흔적이 실제로 그 길이마다 되풀이될 때만 고릅니다. 짧은 곡에서 드문 큰 흔적 몇 개만 골라 7박처럼 보이는 것을 막기 위해서입니다.
옥타브가 다른 같은 음(낮은 도와 높은 도)은 화음에서 같은 역할을 합니다. 그래서 모든 음을 도, 도♯, 레 … 시의 12칸으로 접어 넣으면, 그 순간 울리는 음들의 지문이 생깁니다(크로마). C 코드라면 도, 미, 솔 칸이 두드러집니다.
실제 악기는 한 음만 내지 않습니다. 도를 치면 한 옥타브 위의 도, 그 위의 솔, 다시 도, 미 … 같은 배음이 함께 울립니다. 예를 들어 C 코드의 미에서 나온 배음은 시 칸을 채워서, 배음을 모르는 비교로는 C를 Cmaj7으로 착각합니다. 그래서 코드의 지문도 배음까지 포함해 만들어 두고 비교합니다.
베이스(가장 낮은 음)는 따로 봅니다. 코드는 C인데 베이스가 미라면 C/E로 적습니다.
녹음마다 기준음이 조금씩 다릅니다. A음이 정확히 440 Hz가 아닌 곡도 많습니다. 그래서 곡 전체의 음들이 평균적으로 얼마나 높거나 낮은지 먼저 재고, 그만큼 보정한 다음 12칸에 넣습니다.
박마다 가장 비슷한 코드를 고르기만 하면 멜로디의 지나가는 음 때문에 코드가 박마다 깜빡이듯 바뀝니다. 그래서 두 가지 상식을 확률로 넣습니다. 코드는 자주 바뀌지 않는다, 그리고 바뀐다면 마디 첫 박에서 가장 잘 바뀌고 박 사이에서는 잘 안 바뀐다. 이 규칙과 소리가 얼마나 비슷한지를 함께 따져, 곡 전체에서 가장 그럴듯한 코드 순서를 한 번에 고릅니다(은닉 마르코프 모델과 비터비 알고리즘). 곡의 조성도 추정해서, 조성에 어울리는 코드에 약간의 가산점을 줍니다.
이 메트로놈은 “1분에 97번 똑딱”처럼 일정한 간격으로 치지 않고, 찾아낸 박 하나하나의 시각에 칩니다. 그래서 연주가 조금 빨라지거나 느려져도 따라갑니다.
타이밍도 정밀합니다. 타이머로 “지금 쳐!” 하고 클릭을 내면 컴퓨터가 바쁠 때 수십 ms씩 늦어집니다. 그래서 음악을 재생하는 오디오 장치의 시계에 클릭을 0.2초 앞서 예약해 둡니다. 음악과 클릭이 같은 시계와 같은 출력 경로를 쓰므로 샘플 하나 단위로 맞물리고, 출력이 늦는 장치에서도 둘이 함께 늦게 나와 서로는 어긋나지 않습니다.
녹음된 피아노 소리를 넣으면 파일이 커지고 외부 파일이 필요해집니다. 그래서 피아노 소리를 물리 법칙으로 직접 만들어 씁니다. 피아노 현은 이상적인 줄이 아니라 뻣뻣한 강철선이어서, 배음이 정확한 정수배보다 조금씩 높게 울립니다(비조화성). 해머는 현 길이의 1/7쯤 되는 곳을 때리기 때문에, 그 지점에서 움직이지 않는 배음은 약하게 울립니다. 한 음을 두세 가닥의 현이 함께 내는데, 현끼리 음이 아주 미세하게 달라 소리가 천천히 일렁이고(맥놀이), 처음엔 빨리 줄다가 나중엔 오래 남는 두 단계 감쇠가 생깁니다. 높은 배음일수록 빨리 사라져 소리가 점점 부드러워집니다. 이 성질들을 수식으로 쌓아 한 음씩 합성합니다.
코드는 왼손 베이스 한 음과 오른손 화음으로 나눠 칩니다. 오른손은 앞 코드에서 손이 가장 적게 움직이는 자리바꿈을 골라, 코드가 바뀌어도 소리가 들쭉날쭉하지 않습니다. 피아노는 분석에서 찾은 곡의 기준음에 맞춰 조율되고, 메트로놈과 같은 오디오 시계에 예약되어 코드가 바뀌는 순간에 정확히 울립니다.
곡의 조성은 “주로 어떤 음들이 쓰이는가”로 드러납니다. E♭ 장조 곡은 E♭, F, G, A♭, B♭, C, D를 주로 쓰고, E 장조로 옮기면 E, F♯, G♯, A, B, C♯, D♯를 씁니다. 그래서 비트마다 앞뒤 몇 마디의 음 분포를 24개 조성의 전형적인 분포와 비교하고, 조가 잘 바뀌지 않는다는 규칙을 확률로 넣어 곡 전체에서 가장 그럴듯한 조성의 흐름을 찾습니다. 한두 마디 스쳐 가는 다른 조의 화음(예: C 장조 곡의 E7 → Am)은 전조로 치지 않습니다. 같은 음을 쓰는 나란한 장·단조(C 장조와 A 단조) 사이의 이동도 조표가 바뀌지 않으므로 전조로 치지 않습니다.
전조 위치는 박 단위로 다듬습니다. 새 조의 딸림화음(예: E 장조의 B7)을 거쳐 넘어가면, 악보에서 조표를 바꿔 적는 관례대로 새 으뜸화음이 나오는 자리를 전조 위치로 봅니다. 코드 이름의 ♯/♭ 표기도 그 자리의 조성을 따릅니다. E 장조 구간에서는 A♭m7이 아니라 G♯m7로 적습니다.
flowchart TD
F["오디오 파일"] --> D["브라우저 디코딩<br/>PCM, 장치 샘플레이트"]
D --> PB["재생 버퍼"]
D --> MO["모노 합성"]
MO --> R22["지연 0 리샘플<br/>22,050 Hz"]
R22 --> R11["지연 0 리샘플<br/>11,025 Hz"]
R22 --> RF["리듬 특징<br/>대역별 스펙트럴 플럭스, 음량"]
R11 --> HF["화성 특징<br/>피크 STFT, 튜닝, 크로마"]
RF --> TP["템포 추정<br/>자기상관 콤 점수 × 선호 분포"]
TP --> DP["DP 비트 추적<br/>정밀화, 정리, 연장"]
DP --> PC["정박 위상 검증"]
HF --> PC
PC --> LC["박 단계 검증<br/>두 배, 절반, 4:3, 3:2"]
LC --> A[("분석 결과 A")]
HF --> A
A --> BT["buildTimeline<br/>보정 연산, 박자와 첫 박 HMM, 코드 HMM"]
U["사용자 보정, 설정"] --> BT
BT --> TL[("타임라인 TL")]
TL --> CH["코드 차트 화면"]
TL --> SC["메트로놈 스케줄러"]
TL --> PS["코드 연주<br/>보이싱, 피아노 합성"]
PB --> OUT["리미터, AudioContext 출력"]
SC --> OUT
PS --> OUT
분석은 무거운 단계와 가벼운 단계로 나뉩니다.
analyze() 는 파일을 열 때 한 번만 실행됩니다. 신호 처리 전부와 자동 비트 추적까지 합니다. 보통 몇 초 걸리며, 32 ms마다 화면에 제어권을 넘겨 진행률 표시가 멈추지 않습니다.buildTimeline() 은 보정 버튼이나 설정을 바꿀 때마다 실행됩니다. 박자, 마디, 코드를 다시 계산하는데 5분짜리 곡도 약 40 ms라서 누르는 즉시 반영됩니다.| 단계 | 하는 일 | 주요 값 |
|---|---|---|
| 단시간 푸리에 변환 | 22,050 Hz 신호를 조각마다 스펙트럼으로 | 창 1024점(46.4 ms), 홉 256점(11.6 ms, 초당 86.13 프레임) |
| 로그 압축 | 조용한 소리의 변화도 드러나게 | log(1 + 1000·|X|) |
| 최대값 필터 | 두 프레임 전 스펙트럼을 주파수 방향 ±1칸으로 넓혀 비교 | 비브라토, 음정 미끄러짐을 온셋으로 착각하지 않음 |
| 대역별 플럭스 | 커진 양만 대역별로 평균 | 저역 21–200 Hz(킥, 베이스), 중역 200–2,500 Hz(스네어 몸통, 건반), 고역 2.5–10 kHz(하이햇, 스네어 줄) |
| 온셋 포락선 | 대역마다 표준편차로 나눠 합산 | 고역은 절반 가중(엇박 하이햇의 영향 축소) |
| 음량 | 프레임별 RMS | 소리 구간 판정과 파형 표시에 사용 |
| 단계 | 하는 일 | 주요 값 |
|---|---|---|
| 트레블 피크 STFT | 코드 구성음용 스펙트럼 피크 | 창 2048점(186 ms), 110–4,200 Hz, 프레임당 최대 36개 |
| 베이스 피크 STFT | 베이스 음과 튜닝용 | 창 4096점(372 ms), 32–1,400 Hz, 프레임당 최대 24개 |
| 피크 조건 | 뚜렷한 봉우리만 | 프레임 최댓값 대비 −45 dB 이상, 주변 ±6칸 평균의 2배 이상 |
| 로그 포물선 보간 | 주파수와 크기를 칸 간격보다 정밀하게 | 피크와 양옆 칸의 로그 크기 사용 |
| 기준음 추정 | 모든 피크의 “반음 소수부”를 원 위에서 평균 | 예: +18 cent → A4 = 444.6 Hz |
| 크로마 | 보정한 음높이를 12칸에 누적 | 반음 중심에 가까울수록 큰 가중치(cos²). 트레블은 MIDI 48–96, 베이스는 28–57 |
| 화성 변화 곡선 | 앞뒤 4프레임 크로마의 코사인 거리 | 정박 위상 검증에 사용 |
AudioBufferSourceNode 를 현재 오디오 시각보다 60 ms 뒤에 시작합니다.| 버튼 | 내부 연산 | 동작 |
|---|---|---|
| ½박 옮기기 | half |
이웃 비트의 중간점으로 새 비트 목록을 만들고 온셋 정점에 다시 맞춤. 음악 시작 전으로 삐져나온 박은 버림 |
| 두 배 빠르게 ×2 | x2 |
비트 사이마다 중간점을 추가 |
| 절반으로 ÷2, 셋씩 묶기 ÷3 | d2, d3 |
2개(3개)마다 하나를 남김. 남길 위치는 박 강세 합이 큰 쪽 |
| 한 박 앞당기기, 늦추기 | downShift ∓1 |
마디 안 위치 번호를 한 칸씩 회전 |
| 박자 | meter |
첫 박 HMM을 지정한 박 수로 다시 풂 |
보정은 비트 목록에 대한 연산 기록으로 저장되어 매번 처음부터 순서대로 다시 적용됩니다. “박 보정 되돌리기”는 이 기록을 비우는 것입니다. 코드 종류, 코드 연주 방식, 첫 박 강조, 자동 스크롤, 클릭 타이밍은 곡이 아니라 브라우저에 저장되는 설정이라 “설정 기본값으로”로 따로 되돌립니다. “적용 중”에는 두 가지 모두 기본값과 다른 것만 나옵니다.
| 단계 | 하는 일 |
|---|---|
| 코드 구간 | 엔진이 마디의 코드 이벤트마다 구성음(0 = C)과 연주할 베이스 음을 함께 넘깁니다. 슬래시 표기를 끄면 베이스는 근음입니다. |
| 타건 목록 | 치는 때에 따라 만듭니다. “곡 따라”는 코드가 바뀌는 곳과, 같은 코드라도 원곡에서 새로 친 마디 첫 박(차트의 검은 글자)에서 치고 끌어 둔 곳(회색)에서는 이어서 울립니다. “코드가 바뀔 때”는 마디를 넘어 이어지는 같은 코드를 한 번만, “마디마다 다시”는 마디 첫 박에서 다시, “박마다”는 모든 박에서 칩니다. N.C.에서는 치지 않고 앞 코드도 멈춥니다. |
| 보이싱 | 베이스는 D2~C♯3, 오른손은 구성음 전부를 밀집 배치로 두되 이전 배치의 중심과 가장 가까운 자리바꿈을 고릅니다. |
| 합성과 캐시 | 곡에 쓰이는 음을 처음 나오는 순서대로 한 음씩 미리 합성해 둡니다(음마다 한 번). 곡의 기준음이 바뀌면 다시 만듭니다. |
| 예약 | 메트로놈과 같은 스케줄러가 0.2초 앞의 타건을 오디오 시계에 예약합니다. 클릭 타이밍 보정은 적용하지 않고 음악 시각 그대로 칩니다. |
| 소리 끝 | 코드 구간이 끝나는 시각(다음 타건이나 N.C.)에 댐퍼가 내려오듯 70 ms 시간 상수로 줄어듭니다. |
| 재시작 | 코드 중간에서 재생하거나 이동하면 진행 중인 코드를 곧바로 한 번 울립니다. |
| 믹싱 | 음악, 클릭, 코드 볼륨이 각각 따로 있고, 피아노에는 1.6초짜리 방 잔향을 조금 섞은 뒤 고정 보정 이득(+6.4 dB)을 줍니다. 세 소리는 출력 직전 리미터(−1 dBFS)를 지나 클리핑 없이 합쳐집니다. |
메트로놈과 코드 연주는 재동기화도 따로 합니다. 메트로놈을 끄고 켜거나 클릭 타이밍을 바꿔도 울리고 있는 피아노 코드는 끊기지 않습니다.
기호
| 기호 | 뜻 |
|---|---|
| $F = 22050/256 \approx 86.13$ | 리듬 프레임률(프레임/초) |
| $X(t,k)$ | 프레임 $t$, 주파수 칸 $k$ 의 STFT 값 |
| $\tau$ | 박 주기(프레임 단위) |
| $b_i$ | $i$ 번째 비트 시각(초) |
| $m$ | MIDI 음번호(실수), 마디당 박 수와 문맥으로 구분 |
출력/입력 샘플레이트 비를 최대공약수로 약분해 $L/D = f_\text{out}/f_\text{in}$ 로 두면, $n$ 번째 출력 샘플의 입력 위치는 $nD/L = i_n + \phi_n$ 입니다( $i_n$ 정수부, $\phi_n$ 소수부).
y[n] = \sum_{j=0}^{2W-1} x[\,i_n - W + 1 + j\,]\; h(\phi_n + W - 1 - j)
h(u) = 2f_c\,\mathrm{sinc}(2f_c u)\,w\!\left(\frac{u}{W+1}\right),\qquad
w(r) = 0.42 + 0.5\cos\,\pi r + 0.08\cos\, 2\pi r
f_c = 0.46\,\min\!\left(1, \frac{f_\text{out}}{f_\text{in}}\right),\qquad W = \left\lceil \frac{10}{2f_c} \right\rceil
$h$ 가 우함수(좌우 대칭)이므로 위상이 0, 즉 지연이 0입니다. 위상 $\phi_n$ 은 최대 4,096단계 테이블로 미리 계산하고, 입력 위치는 정수 누산기( $D = qL + r$ )로 갱신해 부동소수점 오차가 쌓이지 않습니다.
$N$ 점 실수 신호를 $N/2$ 점 복소 FFT 한 번으로 계산합니다. $z[m] = x[2m] + i\,x[2m+1]$, $Z = \mathrm{FFT}_{N/2}(z)$ 라 하면
X[k] = \underbrace{\tfrac12\left(Z[k] + \overline{Z[N/2-k]}\right)}_{F_e[k]}
+ e^{-2\pi i k/N}\,\underbrace{\tfrac{-i}{2}\left(Z[k] - \overline{Z[N/2-k]}\right)}_{F_o[k]},\qquad 0 \le k \le N/2
$Z$ 의 인덱스는 $N/2$ 주기로 봅니다( $Z[N/2] = Z[0]$ ).
Y(t,k) = \ln\!\left(1 + \lambda \cdot \tfrac{4}{N}\,|X(t,k)|\right),\qquad \lambda = 1000
\Delta(t,k) = \max\!\Big(0,\; Y(t,k) - \max_{|\nu|\le 1} Y(t-2,\,k+\nu)\Big)
f_b(t-1) = \frac{1}{|B_b|}\sum_{k \in B_b} \Delta(t,k),\qquad b \in \{\text{low}, \text{mid}, \text{high}\}
비교하는 두 프레임( $t$, $t-2$ )의 가운데인 $t-1$ 에 기록합니다. 비트 추적용 온셋 포락선은 다음과 같습니다.
O(t) = \frac{f_\text{low}(t)}{\sigma_\text{low}} + \frac{f_\text{mid}(t)}{\sigma_\text{mid}} + \frac12\,\frac{f_\text{high}(t)}{\sigma_\text{high}}
±3초 창의 음량으로 평준화하고 평균을 뺍니다.
E(t) = \frac{O(t)}{\sqrt{\frac{1}{|W_t|}\sum_{s \in W_t} O(s)^2} + \epsilon} - \bar E,\qquad W_t = [\,t - 3F,\ t + 3F\,]
자기상관은 겹치는 길이로 나누고 $r(0)$ 으로 정규화한 뒤 $[\tfrac14, \tfrac12, \tfrac14]$ 로 평활해 $\tilde r$ 로 씁니다.
r(\ell) = \frac{1}{n-\ell}\sum_{t=0}^{n-\ell-1} E(t)\,E(t+\ell)
BPM 후보 $v$ 마다 박 주기 $\tau_v = 60F/v$ 의 1~4배 지연을 함께 봅니다.
J(v) = \pi(v)\cdot\max\!\Big(0,\ \sum_{k=1}^{4} c_k \max_{|\delta| \le \delta_k} \tilde r(k\tau_v + \delta)\Big),\qquad c = \left(1, \tfrac12, 0.33, \tfrac14\right)
\pi(v) = \exp\!\left(-\frac12 \log_2^2\, \frac{v}{110}\right),\qquad
\delta_1 = 0.5,\quad \delta_{k\ge 2} = 0.5 + 0.004\,k\tau_v
$v \in [40, 240]$ 을 0.25 간격으로 훑고, 최댓값에서 세 점 포물선 보간으로 소수점 BPM을 얻습니다.
온셋을 폭 $\sigma = \tau/32$ 의 가우스로 평활한 국소 점수와 누적 점수는 다음과 같습니다.
\ell(t) = \frac{1}{\sigma_O}\sum_{i} O(t+i)\,e^{-\frac12\left(\frac{32 i}{\tau}\right)^2}
C(t) = \ell(t) + \max_{\tau(t)/2 \,\le\, d \,\le\, 2\tau(t)}\Big[\,C(t-d) - \alpha \ln^2\,\frac{d}{\tau(t)}\,\Big],\qquad \alpha = \mathrm{clip}\!\left(2000\,T^2,\ 100,\ 1600\right)
$T = \tau/F$ 는 박 주기(초)입니다. $\alpha\ln^2(d/\tau) \approx 2000\,(\Delta t)^2$ ( $\Delta t$ 는 간격이 어긋난 초)이므로 벌점이 템포와 상관없이 절대 시간 기준이 됩니다. 원래 방법의 고정값 100은 퍼센트 기준이라, 느린 곡에서는 같은 퍼센트가 훨씬 긴 ms가 되어 박이 싱코페이션에 끌려갔습니다.
국소 박 주기 $\tau(t)$ 는 8초 창(1초 간격)의 자기상관 $r_w$ 로 구하고, 7개 창의 중앙값으로 필인 같은 순간적 흔들림을 거른 뒤 프레임마다 선형 보간합니다.
\tau_w = \arg\max_{\ell \in [0.85\tau,\ 1.15\tau]} \Big( r_w(\ell) + \tfrac12\, r_w(2\ell) \Big),\qquad
\tau(t) = \mathrm{clip}\!\Big(\mathrm{interp}_t\big(\mathrm{median}_{7}\,\tau_w\big),\ 0.85\tau,\ 1.15\tau\Big)
역추적 시작점은 $C$ 의 국소 최댓값 가운데 그 중앙값의 절반을 넘는 마지막 지점입니다. 경로의 앞뒤에서 평활한 국소 점수가 전체 RMS의 절반에 못 미치는 비트는 잘라 냅니다.
도입부 격자(1.10.4). 악기가 적은 도입부에서는 마디 끝에 앞당겨 치는 강세(넷째 박 뒤 8분음표의 베이스·심벌)가 박 사이 온셋보다 훨씬 세서, 동적 계획법이 그 강세를 박으로 삼으며 본 구간보다 몇 % 느린 박으로 흘러가 마디선이 반 박쯤 어긋날 수 있습니다. 그래서 곡 첫 48박 안에서, 어떤 박부터 그 뒤 8박의 주기로 일정하게 거꾸로 이은 격자가 지금 박과 4분의 1박 넘게 벌어지면 두 격자를 견줍니다. 기준은 첫 소리부터 그 박까지의 온셋이 8분음표 자리(정박과 엇박 모두)에 얼마나 맞는지이고, 거꾸로 이은 격자가 0.15 넘게(온셋 무게 평균, 폭 30 ms 가우스) 나을 때만 도입부 박을 그 격자로 바꿉니다. 이때 마디선도 본 구간의 위상을 거꾸로 이어 씁니다. 도입부가 정말 다른 템포나 자유 템포이면 원래 박이 온셋에 더 잘 맞아 그대로 둡니다. 「マリーゴールド」의 도입부(첫 소리 0.55초부터 밴드가 들어오는 7.34초까지)는 0.596초 간격으로 잡혀 3박 못갖춘마디 + 2.55·4.94초 마디선이었는데, 0.566초 간격의 온마디 셋(0.55·2.82·5.08초)으로 바뀌었습니다. 이 마디선은 도입부 리프가 2.26초(4박)마다 되풀이되는 시작점과 5 ms 안에서 맞습니다. GTZAN 1000곡 가운데 이 규칙이 작동한 곡은 레게 한 곡뿐이고 점수는 그대로입니다.
도입부 국소 어긋남(1.11.7). 고정 템포로 만든 곡이라도 드럼 없이 피아노 등으로 시작하면, 도입부에서 16분음표만큼 앞당겨 친 음이 박 사이 온셋보다 세서 동적 계획법이 그 음을 박으로 삼을 수 있습니다. 이때는 도입부 전체의 박 수는 맞아 위의 ‘4분의 1박 넘게 벌어짐’ 조건에는 걸리지 않지만, 박이 도입부 거의 내내 1/4박쯤 앞당겨져 간격이 들쭉날쭉하고 코드 구간도 어긋납니다. 그래서 어떤 박부터 그 뒤 8박이 고른 템포(간격이 주기의 ±3% 안)이고, 그 격자를 일정하게 거꾸로 이었을 때 지금 박 가운데 0.15박 넘게 벗어난 박이 있으면, 도입부 온셋이 16분음표 자리에 얼마나 맞는지로 두 격자를 견줍니다. 거꾸로 이은 격자가 더 잘 맞고, 고정 템포로 만든 곡이라는 근거(도입부 온셋 봉우리 30개 이상, 거꾸로 이은 격자의 16분음표 맞춤 0.85 이상)가 있을 때만 도입부 박을 그 격자로 바꿉니다.
$\pm 2$ 프레임 안의 온셋 최댓값 $j$ 에서 포물선 보간합니다.
\hat t = j + \frac12\cdot\frac{O(j-1) - O(j+1)}{O(j-1) - 2O(j) + O(j+1)},\qquad b = \frac{\hat t}{F} + t_\text{cal},\quad t_\text{cal} = 6.5\ \text{ms}
| 세기가 중앙값의 0.35배 미만인 비트는 좌우 4개 이웃을 가중치 $1/(1+ | j-i | )$ 로 선형 회귀해 위치를 정합니다. 소리 구간이 $[t_s, t_e]$, 박 간격이 $p$ 일 때 비트는 $[\,t_s - p/4,\ t_e + p/4\,]$ 안으로 자르고, 앞쪽은 $b_0 - p \ge \max(0, t_s - p/4)$ 인 동안, 뒤쪽은 $b_\text{last} + p \le t_e - p/2$ 인 동안 같은 간격으로 이어 붙입니다. |
화성 변화 곡선 $\nu$ 는 제곱근 압축 후 L2 정규화한 크로마 $\hat c$ 로 만듭니다.
\nu(t) = \left(1 - \cos\!\Big(\sum_{d=0}^{3}\hat c_{t+d},\ \sum_{d=1}^{4}\hat c_{t-d}\Big)\right)\cdot \min\!\left(1, \sqrt{\frac{\min(e_t, e_{t-1})}{e_{70\%}}}\right)
박 강세 곡선은 소리 구간 평균 $\mu$ 로 정규화한 네 곡선의 가중합입니다.
S(t) = 1.0\,\frac{f_\text{low}}{\mu_\text{low}} + 0.7\,\frac{f_\text{mid}}{\mu_\text{mid}} + 0.2\,\frac{f_\text{high}}{\mu_\text{high}} + 0.8\,\frac{\nu}{\mu_\nu}
\text{on} = \frac1N\sum_i \max_{|\delta|\le 2} S(b_i + \delta),\qquad
\text{off} = \frac1N\sum_i \max_{|\delta|\le 2} S\!\left(\tfrac{b_i + b_{i+1}}{2} + \delta\right)
$\text{off} > 1.1\cdot\text{on}$ 이면 비트 전체를 반 박 옮깁니다(비트 8개 이상일 때).
킥 곡선 $K$ 와 스네어 곡선 $S_n$ 을 정의합니다. $S_n$ 은 중역과 고역의 기하평균이라 두 대역이 동시에 오를 때만 커집니다.
K(t) = \frac{f_\text{low}(t)}{\sigma_\text{low}},\qquad
S_n(t) = \sqrt{\frac{f_\text{mid}(t)}{\sigma_\text{mid}}\cdot\frac{f_\text{high}(t)}{\sigma_\text{high}}}
비트마다 $p_i = K + S_n + \tfrac12 S$ (±2프레임 최댓값)를 구해 짝수 번째와 홀수 번째 평균의 비를 봅니다.
r_2 = \frac{\max(\bar p_\text{even}, \bar p_\text{odd})}{\min(\bar p_\text{even}, \bar p_\text{odd})}
| 교정 | 조건 | 동작 |
|---|---|---|
| 절반으로 | ( $r_2 \ge 2.4$ 이고 $s_{S_n} > -0.1$ , 또는 같은 쪽 치우침) 이고 $\kappa \ge 2$, BPM ≥ 110, BPM/2 ≥ 55 | 박 주기 $2\tau$ 로 처음부터 다시 추적 |
| 두 배로 | BPM ≤ 100, 2·BPM ≤ 210, $\sigma \ge 1.3$, $\kappa \ge 2$ | 중간점을 추가하고 온셋에 맞춤 |
| 두 배로(뒷박형) | BPM ≤ 100, 2·BPM ≤ 200, $c_2 \ge 0.8$, $\sigma \le 0.8$, $\kappa \le 0.95$, $\lvert d_{S_n}\rvert \le 0.25$ | 같음 |
| 두 배로(하프타임형) | BPM ≤ 100, 2·BPM ≤ 210, $c_2 \ge 0.85$, $\sigma \ge 0.9$, $1 \le \kappa \le 10$ | 같음 |
\sigma = \frac{\sum S_n(\text{mid})}{\sum S_n(\text{beat})},\qquad
\kappa = \frac{\sum K(\text{beat})}{\sum K(\text{mid})},\qquad
d_{S_n} = \frac{S_{n,\text{even}} - S_{n,\text{odd}}}{S_{n,\text{even}} + S_{n,\text{odd}}}
mid는 비트 사이 중간점, $c_2$ 는 템포 후보 가운데 지금 템포의 두 배(±8%)인 후보의 점수를 1위 점수로 나눈 값입니다. 뒷박형은 느린 박이 스네어에 앉아 박 위엔 스네어가 박마다 고르게( $d_{S_n} \approx 0$ ), 박 사이엔 킥이 오는 경우이고, 하프타임형은 박 위에 킥, 박 사이에도 스네어가 오는 경우입니다. 하프타임형의 $\kappa \le 10$ 은 박 사이에 저음 타격이 거의 없는 드럼 없는 피아노 곡을 빼기 위한 것입니다. 절반 교정의 $\kappa \ge 2$ 는 비트 사이가 16분음표 자리(저음 타격이 드묾)인지, $s_{S_n} > -0.1$ 은 백비트가 아닌지 확인하는 조건입니다(1.10.0).
같은 쪽 치우침은 짝·홀 비트에서 킥이 강한 쪽을 +로 두고 잰 치우침입니다.
s_X = \mathrm{sgn}(K_\text{even} - K_\text{odd})\cdot\frac{X_\text{even} - X_\text{odd}}{X_\text{even} + X_\text{odd}},\qquad
\text{same side} \iff s_K \ge 0.15\ \land\ s_{S_n} \ge 0.15\ \land\ s_\nu > 0\ \land\ r_2 \ge 1.3
제 박에서는 백비트 때문에 스네어가 킥의 반대쪽에 오므로 $s_{S_n} < 0$ (합성 곡 −0.28 ~ −0.61)이고, 두 배로 잡힌 경우에는 둘 다 진짜 박 쪽에 오므로 $s_{S_n} > 0$ 입니다.
두 배 교정이 훨씬 엄격한 까닭은 위험이 비대칭이기 때문입니다. 잘못 절반으로 줄이면 클릭이 성겨질 뿐이지만, 잘못 두 배로 늘리면 추가된 클릭 전부가 엇박이 됩니다.
비트 구간을 24등분한 위상마다 온셋 최댓값을 평균한 프로필 $P(\varphi)$ 에서
\text{triple} \iff \min\!\big(P(\tfrac13), P(\tfrac23)\big) > 1.15\,P(\tfrac12)\ \ \land\ \ \tfrac12\big(P(\tfrac13) + P(\tfrac23)\big) > 1.1\,\mathrm{median}(P)
4:3 박 단계 확인(1.11.5). 16분음표가 촘촘한 느린 4/4 곡은 16분음표 셋(점8분음표)마다 온셋이 되풀이되는 것처럼 보여, 템포 선호 분포(110 BPM 중심) 때문에 실제 템포(예: 69 BPM)의 4/3배(92 BPM)가 뽑히고 한 박이 셋으로 쪼개진 12/8로 적힐 수 있습니다. 박 세분 판정이 3분할이면, 지금 비트의 템포 $b$ 의 3/4 근처(±4%)에서 선호 분포를 곱하기 전 콤 점수 $C$ 가 가장 큰 템포 $b’$ 로 비트를 다시 추적해 보고, 다음을 모두 만족할 때만 바꿉니다.
\frac{C(b')}{C(b)} \ge 0.93,\qquad
\frac{C(b')\,\pi(b')}{C(b)\,\pi(b)} \ge 0.85,\qquad
\frac{P'(\tfrac12)}{\tfrac12\big(P'(\tfrac13) + P'(\tfrac23)\big)} \ge 1.4,\qquad
G = \rho(8) + \rho(16) - \rho(12) - \rho(24) \ge 0,\qquad
\pi(x) = e^{-\frac12\log_2^2(x/110)}
첫째 조건은 선호 분포를 빼면 느린 템포가 온셋 주기를 지금 템포와 거의 같거나 더 잘 설명한다는 뜻이고, 둘째는 선호 분포를 곱한 처음 템포 추정에서도 두 후보가 거의 비겼다는 뜻입니다. $P’$ 는 새 비트로 잰 박 세분 프로필로, 새 박이 셋잇단이 아니라 8분음표 둘로 뚜렷하게 나뉘어야 합니다. $\rho(L)$ 은 지금 박을 셋으로 나눈 격자(16분음표) 위에서 앞뒤 4칸의 트레블·베이스 크로마 거리로 만든 화음 바뀜 열의 지연 $L$ 자기상관이라, $G$ 는 화음이 8·16칸(4/4의 반 마디·마디)마다 바뀌면 양수, 6·12칸(12/8의 반 마디·마디)마다 바뀌면 지연 12·24에서 되풀이되어 음수입니다. 새 템포는 50~140 BPM이어야 합니다. 다시 잡은 비트가 킥만 따라 엇박에 붙은 경우(박 사이 가운데의 스네어가 박 위보다 세고 킥은 박 위가 셈: $S_n(\text{beat})/S_n(\text{mid}) < 0.9$ 이고 $K(\text{beat})/K(\text{mid}) > 1$)에는 반 박 옮깁니다. 처음에는 첫째 조건만 두었는데 기타 반주 곡과 아주 빠른 곡 8곡이 나빠져 셋째·둘째 조건을 차례로 더해 좁혔고, 넷째 조건은 바뀌는 곡을 줄이지 않는 안전 여유입니다(4-6의 1.11.5 검증).
3:2 박 단계 확인(1.11.6). 반대로 8분음표가 고르게 이어지는 곡(독주 기타 반주, 8비트 록 등)은 8분음표 셋(점4분음표)마다 강세가 되풀이되는 것처럼 보여, 실제 템포의 2/3배로 잡히고 12/8·6/8로 적힐 수 있습니다. 4:3 확인으로 바꾸지 않은 곡은 지금 템포 $b$ 의 3/2 근처(±4%)에서 $C$ 가 가장 큰 템포 $b’’$ 로 다시 추적해 보고, 다음을 모두 만족할 때만 바꿉니다.
\frac{C(b'')}{C(b)} \ge 0.93,\qquad
\frac{P''(\tfrac12)}{\tfrac12\big(P''(\tfrac13) + P''(\tfrac23)\big)} \ge 1.4,\qquad
G \ge 0.05,\qquad
\frac{\bar S(\text{beat})}{\bar S(\text{mid})} \ge 1.05,\qquad
90 \le b'' \le 240
$G$ 는 4:3 확인과 같은 지표입니다(여기서는 지금 박을 셋으로 나눈 칸이 8분음표라, 화음이 4/4의 한 마디·두 마디인 8·16칸마다 바뀌면 양수). 넷째 조건은 다시 잡은 비트 위치의 강세 $S$ 가 비트 사이 가운데보다 5% 넘게 세야 한다는 것으로, 템포는 맞았지만 비트가 엇박에 붙은 경우를 거릅니다. 반 박 옮김 규칙은 4:3 확인과 같습니다. 새 템포가 90 BPM보다 느린 경우(지금 템포가 60 BPM 미만)는 실제로 느린 셋잇단 발라드가 대부분이라 보지 않습니다. 처음에는 범위와 $G$ 조건으로 시작했는데 좋아지는 곡 26곡에 나빠지는 곡이 22곡이라, 점수 비·강세 비·8분음표 나뉨 조건을 차례로 더해 나빠지는 곡이 없을 때까지 좁혔고, 코드 채점에서 장·단이 나빠진 곡이 하나 나와 템포 하한을 90 BPM으로 올렸습니다(4-6의 1.11.6 검증).
| 로그 크기 $L_-, L_0, L_+ = \ln | X(k-1) | , \ln | X(k) | , \ln | X(k+1) | $ 로 |
\delta = \frac12\cdot\frac{L_- - L_+}{L_- - 2L_0 + L_+},\qquad
\hat f = (k + \delta)\frac{f_s}{N},\qquad
\hat A = \frac{4}{N}\exp\!\left(L_0 - \tfrac14(L_- - L_+)\,\delta\right),\qquad
m = 69 + 12\log_2\,\frac{\hat f}{440}
기준음 편차(반음 단위)는 소수부 $d_j = m_j - \mathrm{round}(m_j)$ 의 원형 평균입니다(베이스 STFT의 MIDI 40–90 피크, 가중치 $\sqrt{A_j}$ ).
\theta = \frac{1}{2\pi}\mathrm{atan2}\!\Big(\sum_j \sqrt{A_j}\sin\, 2\pi d_j,\ \sum_j \sqrt{A_j}\cos\, 2\pi d_j\Big),\qquad
A4 = 440 \cdot 2^{\theta/12}\ \text{Hz}
보정한 음높이 $\tilde m = m - \theta$ 로 크로마를 쌓습니다. $w(\cdot)$ 는 트레블과 베이스 각각의 음역 창입니다.
C(t, q) = \sum_{j:\ \mathrm{round}(\tilde m_j) \bmod 12 = q} A_j\, w(\tilde m_j)\, \cos^2\!\big(\pi(\tilde m_j - \mathrm{round}(\tilde m_j))\big)
박 구간 $[t_0, t_1)$ 의 프레임을 가운데 가중으로 평균합니다.
\bar C(q) = \frac{\sum_j \omega_j\,C(j, q)}{\sum_j \omega_j},\qquad \omega_j = 0.1 + \sin(\pi u_j),\quad u_j = \frac{j h - t_0}{t_1 - t_0}
베이스 크로마의 상시 성분 제거와 조성 추정은 다음과 같습니다. $\vec{K}_\text{mode}$ 는 Krumhansl–Kessler 장조, 단조 프로필이고 $\mathrm{rot}_t$ 는 으뜸음 $t$ 로의 회전입니다.
B'_s(q) = \max\!\Big(0,\ B_s(q) - \tfrac12\mathrm{median}_i B_i(q)\Big)
(\hat t, \widehat{\text{mode}}) = \arg\max_{t,\ \text{mode}}\ \mathrm{corr}\!\big(\vec{h},\ \mathrm{rot}_t(\vec{K}_\text{mode})\big),\qquad
h_q = \sum_i \sqrt{\frac{T_i(q)}{\sum_{q'} T_i(q')}} + \frac12\sqrt{\frac{B_i(q)}{\sum_{q'} B_i(q')}}
배음 오프셋 $d = (0, 12, 19, 24, 28, 31)$ 반음, 가중치 $0.6^{h-1}$ 로 구성음과 베이스 누설( $\rho = 0.25$ )을 더합니다.
\tilde T(q) = \sum_{p \in \text{chord}}\ \sum_{h=1}^{6} 0.6^{h-1}\,[\,q \equiv p + d_h\,] \;+\; \rho\sum_{h=1}^{6} 0.6^{h-1}\,[\,q \equiv p_\text{bass} + d_h\,]\pmod{12}
T = \frac{(\tilde T/\max\tilde T)^{\gamma}}{\left\|(\tilde T/\max\tilde T)^{\gamma}\right\|_2},\qquad \gamma = 0.45
관측 $o_s$ 도 트레블 크로마를 같은 방식(최댓값 정규화, $\gamma$ 제곱, L2 정규화)으로 만듭니다. 코드 $c$ 의 방출 점수는 다음과 같습니다.
e_s(c) = \beta\,\langle o_s, T_c\rangle + \pi_q + \pi_\text{inv} + \kappa\,[\,c \subset \text{key}\,]
+ \eta\,g_s\Big(\ln\big(0.06 + \hat b_s(p_\text{bass})\big) - \ln\big(0.06 + \tfrac{1}{12}\big)\Big)
$\beta = 30$, $\kappa = 0.6$, $\eta = 1.5$ 이고, $\hat b_s$ 는 합이 1인 베이스 크로마, $g_s = \min!\big(1, E^\text{bass}s / (0.5\,E^\text{bass}{60\%})\big)$ 는 베이스가 약할 때 베이스 항을 줄이는 계수입니다. 코드 없음(N)의 점수는 크로마가 평평할수록, 소리가 작을수록 커집니다.
e_s(\mathrm{N}) = \beta\left(\frac{1}{\sqrt{12}}\sum_q o_s(q) - 0.12\right) + Q_s,\qquad
Q_s = \begin{cases} 12 & r_s < 0.02 \\ 6\,(0.08 - r_s)/0.06 & r_s < 0.08 \\ 0 & \text{otherwise}\end{cases}
$r_s$ 는 슬롯의 트레블 에너지를 전체 60백분위수로 나눈 값입니다. 사전 점수 $\pi_q$ 와 전위 사전 점수 $\pi_\text{inv}$ 는 아래와 같습니다.
| 성질 | maj | min | 7 | maj7 | m7 | sus4 | sus2 | dim | aug | m7♭5 | dim7 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| $\pi_q$ | 0 | 0 | −0.4 | −0.6 | −0.4 | −1.2 | −1.4 | −1.3 | −1.8 | −1.2 | −1.6 |
| 베이스 | 근음 | 3음 | 5음 | 7음 |
|---|---|---|---|---|
| $\pi_\text{inv}$ | 0 | −0.7 | −1.0 | −1.0 |
전위는 maj, min, 7, maj7, m7에만 둡니다. 어휘별 상태 수는 기본 73개, 표준 241개, 확장 289개(N 포함)입니다.
노래 선율 줄이기(1.11.0). 코드를 틀리는 가장 큰 원인은 노래 선율이 화음 대역에 섞여 공통음이 많은 이웃 코드(C ↔ Am, G7 ↔ Gm7 등)로 읽히는 것입니다. 대중음악 녹음은 보통 노래를 가운데에, 반주를 좌우로 벌려 두므로, 왼쪽에서 오른쪽을 뺀 곁 신호 $S = (L - R)/2$ 에서는 노래가 거의 지워지고 반주가 남습니다. 그래서 곁 신호로 트레블 크로마를 한 번 더 만들어 최종 코드 판정의 트레블 크로마에 더합니다($C_\text{treble} = C_M + C_S$). 베이스도 가운데에 있어 곁 신호에서는 베이스 음의 배음이 빠지므로, 구간 베이스 크로마에서 센 음의 트레블 성분을 반 비중으로 되살립니다. 이렇게 섞은 크로마는 근음과 장·단 갈래를 고를 때만 쓰고, 고른 근음·갈래가 섞기 전 크로마로 읽은 코드와 같으면 7화음·자리바꿈 이름은 섞기 전 쪽을 씁니다. 박·첫 박·박자·조성 추정에는 섞지 않습니다. 모노 파일이나 두 채널이 거의 같은 파일(곁/가운데 에너지 −30 dB 미만)은 곁 신호를 만들지 않으므로 결과가 그대로입니다. 스테레오 파일은 분석 시간이 늘어납니다.
되풀이 맞추기(1.11.0). 대중가요는 1·2절과 후렴처럼 같은 코드 진행이 되풀이되고, 그때마다 선율과 필인만 달라집니다. 박마다 트레블·베이스 크로마 단위벡터로 곡 안 유사도를 재고, 8박 창으로 평균한 대각선(16박 이상 떨어진 같은 자리)이 곡 안 상위 3%를 넘으면서 그 자리에서 가장 잘 맞는 곳을 그 박의 ‘되풀이 짝’으로 삼습니다(마디 안 박 위치가 같은 짝만, 박마다 6개까지). 마지막 후렴이 반음·온음 올라간 곡은 조 차이만큼 옮겨서도 비교합니다. 최종 코드 비터비 전에 박마다 코드 점수를 짝들의 점수와 가중 평균하되(자기 1, 짝 하나 0.7 × 유사도), 짝에게서는 근음별 최고 점수만 빌리고 장·단·7·sus 같은 성질은 그 박 자신의 소리로 정합니다. 한 번의 선율 탓에 이웃 코드로 기운 박을 다른 되풀이가 바로잡고, 다르게 바꿔 친 코드의 성질까지 끌어오지는 않습니다. 유사도 표가 박 수의 제곱 크기라 3,000슬롯이 넘는 아주 긴 입력에는 쓰지 않습니다.
반감7 이름(1.11.1). 표준 어휘에는 m7♭5가 없어서(넣으면 다른 곡의 m7·dim이 바뀌어 전체가 나빠짐) 반감7화음(단조 ii°7, 장조 vii°7)이 dim으로 적혔습니다. 코드와 조성을 다 정한 뒤, dim으로 적힌 구간마다 섞기 전 트레블 크로마의 단7음(근음 + 10반음)이 3화음 구성음 평균의 0.5배 이상이면 이름만 m7♭5로 바꿉니다. 근음·장단·경계·조성은 그대로입니다. GuitarSet에서 연주한 코드를 기준으로 성질이 맞는 표본이 59.37%에서 60.28%로 늘었고(+97 / −3, 대중음악 장르는 +69 / −0), 바뀐 표본의 대부분이 연주자가 실제로 친 m7♭5였습니다.
전이 점수가 “유지 0, 바꾸면 $-\lambda_s$ (도착 코드와 무관)”이므로 매 단계의 최댓값 하나만으로 모든 도착 상태의 최선 전이를 구할 수 있습니다.
V_s(c) = e_s(c) + \max\!\Big(V_{s-1}(c),\ \max_{c'} V_{s-1}(c') - \lambda_s\Big)
| 슬롯 위치 | 마디 첫 박 | 짝수 박자의 가운데 박 | 그 밖의 박 | 박 사이(½박 해상도) | 예비 코드(모든 박) |
|---|---|---|---|---|---|
| $\lambda_s$ | 1.5 | 3.0 | 4.0 | 6.0 | 3.0 |
박 $i$ 의 여섯 특징을 각각 표준화( $Z$ )해 더합니다.
z_i = 1.0\,Z(h)_i + 1.0\,Z(l)_i + 0.5\,Z(g)_i + 0.6\,Z(k)_i + 0.3\,Z(e)_i + 0.4\,Z(o)_i
| 기호 | 특징 |
|---|---|
| $h_i$ | 트레블 크로마 변화 $1 - \langle \hat t_i, \hat t_{i-1}\rangle$ |
| $l_i$ | 예비 코드가 바뀌었는가 |
| $g_i$ | 베이스 크로마 변화 |
| $k_i$ | 박 위치의 저역 온셋 세기 |
| $e_i$ | 음량 증가량(dB, 양수만) |
| $o_i$ | 으뜸화음으로 새로 진입했는가 |
상태는 마디 안 위치 $p \in {0, \dots, m-1}$ 입니다. 방출 점수는 $p = 0$ 에서 $1.5\,z_i$, 짝수 박자( $m \ge 4$ )의 $p = m/2$ 에서 $0.45\,z_i$, 나머지는 0입니다.
P(p \to p+1) = 1 - 2\varepsilon,\qquad P(k \to 0) = \varepsilon\ \ (1 \le k \le m-2),\qquad P(m-1 \to m-1) = \varepsilon,\qquad \varepsilon = 3\times 10^{-4}
작은 확률의 마디 단축과 연장을 허용해 변박이나 잘린 마디에도 경로가 무너지지 않습니다. 박자 자동 선택은 대비도 $\Delta_m$ (첫 박으로 고른 박의 $z$ 평균 − 나머지 박의 $z$ 평균)로 합니다. 후보는 4박과 3박(겹박자면 4와 2)이고, 4박보다 대비도가 0.25(겹박자 0.3) 이상 높을 때만 다른 쪽을 고릅니다.
드럼 짝·홀 사전(1.10.0). 박마다 킥 세기 $K$ 와 스네어 세기 $S_n$ 을 표준화하고, 앞뒤 8박 안에서 $(-1)^j$ 를 곱해 더한 값을 절댓값 합으로 나눈 국소 일관도를 구합니다(스네어는 부호를 뒤집음).
c^X_i = (-1)^i\,\frac{\sum_{|j-i| \le 8} (-1)^j X_j}{\sum_{|j-i| \le 8} |X_j|} \in [-1, 1],\qquad
z_i \leftarrow z_i + \mathrm{sgn}(c^K_i)\,\min\big(|c^K_i|, |c^{-S_n}_i|\big)\quad \text{if } |c^K_i|, |c^{-S_n}_i| \ge 0.3,\ c^K_i\,c^{-S_n}_i > 0
첫 박은 킥 쪽 박(1·3박)이지 스네어 쪽(2·4박)이 아니라는 사전입니다. 1박과 3박의 구분은 여전히 화성 증거가 정합니다. 킥과 스네어가 둘 다 뚜렷하게 엇갈리고 서로 반대쪽일 때만 쓰므로, 독주 기타의 스트로크처럼 한쪽만 엇갈리는 곳에는 0입니다. 곡의 20% 이상 박에서 이렇게 확인되면(드럼 세트가 있음), 저역이 베이스나 당김 강세로 흐린 곳에서는 스네어 일관도(0.5 이상)만으로도 더합니다. GTZAN 팝에서 이 사전이 가리킨 짝·홀은 정답 첫 박의 짝·홀과 99.6% 맞았습니다.
박자 선택 보강(1.10.0). 3·5·7박은 대비도 조건에 더해 첫 박 증거의 자기상관이 그 마디 길이에서 4박보다 클 때만 고르고, 드럼 백비트가 곡의 30% 이상에서 확인되면 3·5·7박 후보를 뺍니다(곡 중간의 박자 변화 모델은 그대로). 4박 대 2박(반 마디) 판정은 드럼 사전을 더하기 전의 $z$ 로 합니다. 킥이 1·3박을 똑같이 올려 4/4가 2/4로 읽히지 않게 하기 위해서입니다.
\rho_z(m) + \rho_z(2m) > \rho_z(4) + \rho_z(8)
길이가 다른 마디 다시 보기(1.11.4). 마디 첫 박 코드를 반 박~한 박 앞당겨 치는 곡은 박 단위 화성 변화 $h, l, g$ 가 넷째 박과 첫 박에 나뉘어, 첫 박 증거가 셋째 박을 첫 박처럼 가리키고 마디선이 반 마디 밀리며 2박짜리 마디가 끼기 쉽습니다. 고정 박자 풀이에 길이가 다른 마디가 있고 곡 중간 박자 변화가 없으면, 박 단위 변화 대신 앞 2박과 뒤 2박 창의 트레블·베이스 크로마 거리 $h^{(2)}_i, g^{(2)}_i$ (앞당긴 바뀜도 첫 박에 모임)로 바꾼 증거로 다시 풉니다.
z'_i = z_i - Z(h)_i - Z(l)_i - 0.5\,Z(g)_i + Z(h^{(2)})_i + Z(g^{(2)})_i,\qquad
h^{(2)}_i = 1 - \cos\Big(\textstyle\sum_{j=i-2}^{i-1} \hat T_j,\ \sum_{j=i}^{i+1} \hat T_j\Big)
| 다시 푼 결과는 세 가지가 모두 맞을 때만 씁니다(박자를 자동으로 고른 4/4 곡과 4/4를 직접 고른 곡. 첫 박 이동은 고친 격자를 그대로 옮김). ① 길이가 다른 마디가 하나도 없음(곡 전체가 한 마디 위상), ② 원래 증거 $z$ 와 새 증거 $z’$ 모두에서 곡 전체를 한 위상으로 볼 때 그 위상의 첫 박 점수가 다른 위상보다 길이가 다른 마디 두 개의 값($2\, | \ln \varepsilon | $) 넘게 높음, ③ 같은 악구가 4·8마디 뒤에 되풀이될 때 새 격자가 고른 같은 자리 박의 화성이 원래 격자가 고른 박보다 더 비슷한 경우가 부호 검정 $z \ge 3$ 으로 많음(같은 악구는 마디 위상도 같아야 하므로, 원래 풀이의 위상 뒤집힘이 가짜라는 근거). 조건 없이 쓰면 GTZAN에서 첫 박이 좋아진 곡(71)보다 적지만 나빠진 곡(34)이 있어, 기존 곡이 하나도 나빠지지 않게 조건을 좁혔습니다. |
곡 첫머리 위상(1.10.3). 곡이 못갖춘마디로 시작한 것으로 판정됐는데 처음 두 마디 안에 짧은 마디가 끼어 있고, 곡 처음부터 그 짧은 마디 끝까지의 박 수가 마디 길이의 배수이면, 첫 박 위상을 한 박 늦게 잡았다가 짧은 마디로 맞춘 경우입니다(가짜 못갖춘마디 + 가짜 “3박” 마디). 이때는 곡 처음부터 온마디로 다시 셉니다. GTZAN 대중 6장르의 불규칙 마디가 711개에서 695개로 줄고 첫 박 F는 그대로입니다.
오디오 시계에서 음악을 $t_s$ 에 곡 위치 $o_s$ 부터 재생하기 시작했다면, 사용자 보정 $\delta$ 를 더한 $i$ 번째 클릭의 예약 시각과 현재 재생 위치, 화면 표시 위치는 다음과 같습니다.
t^\text{click}_i = t_s + (b_i + \delta - o_s),\qquad
\text{pos} = o_s + \max(0,\ t_\text{now} - t_s),\qquad
\text{pos}_\text{display} = \text{pos} - L_\text{out}
스케줄러는 주기 $T_\text{tick} = 25$ ms로 깨어나 $b_i + \delta \le \text{pos} + H$ 인 클릭을 예약합니다( $H = 0.2$ s, 백그라운드 탭은 1.5 s). $H \gg T_\text{tick}$ 이므로 타이머가 한 번에 약 175 ms 이상 늦지 않는 한 클릭은 이미 예약되어 있습니다. 클릭 소리는 다음 파형을 미리 계산해 둔 버퍼입니다(첫 박 $f$ = 1,850 Hz·$A$ = 0.9, 나머지 1,250 Hz·0.6, 앞 2 ms에 선형 감쇠 잡음을 더함). 코드 변화 단위가 ½박이면 박 $b_i$ 를 예약할 때 그 뒤 박 사이 $(b_i + b_{i+1})/2 + \delta$ 에 작은 클릭(2,600 Hz, $A$ = 0.3, 감쇠 8 ms, 잡음 절반)을 함께 예약합니다. 반 박 뒤의 소리라 느린 곡에서도 늦지 않고, 위치를 옮기면 지금 박의 뒤쪽 클릭부터 다시 예약합니다(1.10.0).
x(t) = A\Big[\,0.75\,\big(\sin\, 2\pi f t + 0.3\sin\, 2\pi (2.02 f) t\big)\,e^{-t/16\,\text{ms}}\Big]
MIDI 음 $m$ 과 곡의 기준음 편차 $\theta$ (반음)로 기본 주파수와 비조화 배음을 정합니다.
f_0 = 440 \cdot 2^{(m - 69 + \theta)/12},\qquad f_k = k f_0\sqrt{1 + B k^2},\qquad B = 3.5\times 10^{-4}\cdot 2^{(m-60)/24}
해머 타격 위치 $x_h = 1/7.5$ 와 해머 펠트의 고역 감쇠로 배음 크기를 정합니다.
a_k = \frac{|\sin(\pi k x_h)|}{k^{0.8}}\cdot\frac{1}{1 + (f_k/f_b)^2},\qquad f_b = \mathrm{clip}\big(2600 + 40(m-60),\ 1800,\ 5000\big)\ \text{Hz}
각 배음은 빠른 감쇠(즉음)와 느린 감쇠(여음)의 합이고, 낮은 배음은 여러 현 $s$ 가 조금씩 다른 음정 $d_s$ 로 함께 울립니다.
y(t) = \sum_{k}\ \sum_{s=1}^{S_k} \frac{a_k}{S_k}\left(0.60\,e^{-t/\tau^{F}_k} + 0.40\,e^{-t/\tau^{S}_k}\right)\sin\!\left(2\pi f_k\, 2^{d_s/1200}\, t + \varphi_k\right)
\tau^{S}_k = \max\!\left(0.12,\ \tau_1\Big(\frac{f_0}{f_k}\Big)^{0.7}\right),\qquad \tau^{F}_k = 0.15\,\tau^{S}_k,\qquad \tau_1 = \mathrm{clip}\!\left(8.5\cdot 2^{-(m-38)/16},\ 1.0,\ 8.5\right)\ \text{s}
현의 수 $S_k$ 는 $k \le 6$ 에서 1(m < 35), 2(m < 47), 3(그 밖)이고 $k > 6$ 은 1입니다. 현별 조율 차는 $d = (0, +1.0, -0.8)$ cent입니다. 배음은 32번째, 8 kHz까지만 쓰고 −100 dB 아래로 내려간 뒤는 계산하지 않습니다. 시작 위상 $\varphi_k$ 는 음마다 고정된 난수로 배음마다 다르게 두되, 같은 배음의 여러 현은 같은 위상으로 출발시킵니다(4-2의 맥놀이 참고). 여기에 30 ms 해머 잡음, 2 ms 어택 램프, 끝 60 ms 페이드를 더하고, 앞 0.3초의 RMS가 0.12가 되도록 맞춥니다. 길이는 $\mathrm{clip}(5.6 - 0.07(m-38),\ 2.6,\ 5.6)$ 초입니다. 피아노 경로 전체에는 고정 보정 이득 +6.4 dB(×2.09)를 곱합니다. 사인은 Math.sin 대신 회전 재귀로 만들고 2,048 샘플마다 크기를 1로 되돌립니다.
\begin{pmatrix} c_{i+1} \\ s_{i+1} \end{pmatrix} =
\begin{pmatrix} \cos\,\omega & -\sin\,\omega \\ \sin\,\omega & \cos\,\omega \end{pmatrix}
\begin{pmatrix} c_i \\ s_i \end{pmatrix},\qquad \omega = \frac{2\pi f}{f_s}
오른손 후보는 구성음의 자리바꿈마다 맨 아래 음을 F3~E4(53~64)에 두고 위로 쌓은 밀집 배치입니다.
n_\text{bass} = 38 + \big((p_\text{bass} - 38) \bmod 12\big),\qquad
\hat V = \arg\min_{V}\ \big|\bar V - \mu\big| + 2\max(0,\ V_\text{top} - 77),\qquad
\mu \leftarrow 0.7\,\bar{\hat V} + 0.3\cdot 62
$\bar V$ 는 배치의 평균 음높이이고, $\mu$ 는 D4(62)에서 시작해 직전 배치를 따라가되 가운데로 조금씩 당겨집니다. 세기는 베이스 0.62, 오른손 음마다 $0.5\sqrt{3/n}$ ( $n$ 은 오른손 음 수), 좌우 위치는 $\mathrm{clip}((m-60)/36,\ -0.5,\ 0.5)$ 입니다. 타건 시각 $t_j$ 는 (15)와 같은 방식으로 오디오 시계에 옮기되 사용자 클릭 보정 $\delta$ 는 더하지 않고, 코드 구간이 끝나는 시각 $t^\text{end}_j$ 부터 음량을 시간 상수 70 ms로 줄입니다.
t^\text{piano}_j = t_s + (t_j - o_s),\qquad g(t) = g_0\,e^{-(t - t^\text{end}_j)/70\,\text{ms}}\quad (t \ge t^\text{end}_j)
비트 $i$ 의 음 분포 $h_i$ 와 앞뒤 ±6박을 삼각 가중으로 모은 $H_i$ 를 만듭니다.
h_i(q) = \sqrt{\frac{T_i(q)}{\sum_{q'} T_i(q')}} + \frac12\sqrt{\frac{B_i(q)}{\sum_{q'} B_i(q')}},\qquad
H_i = \sum_{|j-i| \le 6}\Big(1 - \frac{|j-i|}{7}\Big)\,h_j
조표 부류 $g$ (장조 으뜸음 $g$, 나란한 단조 $g+9$)의 점수는 두 조성 프로필과의 상관 중 큰 값입니다.
c_i(g) = \max\Big(\mathrm{corr}\big(H_i,\ \vec K_\text{major}(g)\big),\ \mathrm{corr}\big(H_i,\ \vec K_\text{minor}(g+9)\big)\Big),\qquad g = 0, \dots, 11
조가 바뀔 때만 벌점 $\Lambda$ 를 주는 12상태 HMM을 코드 비터비와 같은 $O(T \cdot 12)$ 방식으로 풉니다.
V_i(g) = c_i(g) + \max\Big(V_{i-1}(g),\ \max_{g'} V_{i-1}(g') - \Lambda\Big),\qquad \Lambda = 6
24박보다 짧은 구간은 $\sum c_i$ 가 더 큰 이웃 구간에 흡수합니다. 구간마다 장조와 나란한 단조 중 구간 전체 분포와 상관이 큰 쪽을 고릅니다. 경계는 창으로 모으지 않은 비트별 점수 $u_j$ 로 ±8박 안에서 다시 찾습니다.
b^\ast = \arg\max_{|b - b_0| \le 8}\ \sum_{j=b-16}^{b-1}\big(u_j(A) - u_j(B)\big) + \sum_{j=b}^{b+15}\big(u_j(B) - u_j(A)\big) + 0.5\,[\,b\ \text{is a downbeat}\,]
$b^\ast$ 의 코드가 새 조의 V, V7, Vsus4 이고 8박 안에 새 조의 으뜸화음(장조 I·Imaj7, 단조 i·im7)이 이어지면, 경계를 그 으뜸화음 자리로 옮깁니다. 이렇게 나눈 구간의 조성이 코드 방출 점수 (12)의 조성 가산점과 음이름 표기에 쓰입니다.
코드 인식이 끝나면 구간마다 코드로 조성을 다시 정합니다. 구간의 코드열(같은 코드는 하나로 묶고 길이 $\ell_j$ 박)과 후보 조성 $K$ 에 대해 다음 점수가 가장 큰 조성을 24개 중에서 고릅니다.
J(K) = \frac{\sum_j \ell_j\,[\,c_j \subset K\,]}{\sum_j \ell_j} + 0.5\,\frac{\sum_j \ell_j\,[\,c_j = \mathrm{I}_K\,]}{\sum_j \ell_j} + 0.3\min\!\Big(1, \frac{N_{V \to I}}{3}\Big) + 0.15\,[\,c_1 = \mathrm{I}_K\,] + 0.15\,[\,c_\text{last} = \mathrm{I}_K\,] + 0.4\,\frac{N_\text{phrase}(\mathrm{I}_K)}{N_\text{phrase}} + 1.2\,\mathrm{corr}\Big(\sum_i h_i,\ \vec K\Big)
이렇게 다시 정한 조가 이웃한 구간끼리 같으면 한 구간으로 합칩니다(1.10.0). 구간을 나눈 조표 부류는 달라도 코드로 다시 정한 조가 같을 수 있어, 그대로 두면 조가 바뀌지 않았는데 전조로 적히기 때문입니다. 이웃한 구간이 나란한 장·단조(G 장조와 E 단조)로 갈려도 한 구간으로 합쳐 합친 구간의 코드로 조를 다시 정합니다. 나란한조 사이 이동은 전조로 적지 않는다는 관례대로이고, 이렇게 갈린 곡은 장·단조 판단이 애매한 곡이므로 합친 구간에서는 단조 점수가 나란한 장조보다 0.2 넘게 높을 때만 단조로 적습니다(대중음악은 장조가 흔함, 1.10.0).
거의 동점인 전조 거르기(1.11.1). 구간마다 코드로 조를 다시 정할 때, 한 구간이 고른 조가 이웃 구간의 조(또는 그 나란한 조 가운데 이 구간에서 점수가 높은 쪽)보다 $J$ 로 0.1 미만만 앞서면 그 구간도 이웃의 조로 봅니다. 근거가 거의 없는 전조가 나란한 조 합치기를 막아 곡 전체의 조 이름이 연쇄로 어긋나는 것을 막기 위해서입니다. 1.11.0에서 「고민중독」 도입부가 D 장조 1.788 대 G 장조 1.781로 갈려 곡 전체가 B 단조 → E 단조 → F 단조로 적히던 것이 이 규칙으로 G 장조 → A♭ 장조(정답)로 돌아옵니다. 시험한 모든 자료에서 이 차이 안에 드는 경우는 세 번(0.007, 0.02, 0.036)뿐이었고, 진짜 전조는 0.7 넘게 앞섰습니다.
m7과 6화음이 겹치는 조 이름(1.11.4). 장조 곡의 vi7(F♯ 장조의 D♯m7)은 I6(F♯6)과 구성음이 같아 코드로는 가를 수 없는데, 으뜸화음 점수는 단조 쪽(D♯ 단조의 i7)에만 들어가 vi7이 많은 장조 곡이 나란한 단조 이름으로 적힐 수 있습니다. 코드로 고른 조가 단조이고, 구간의 음 분포가 나란한 장조와 가장 잘 맞으며(24개 조 가운데 1위), 그 차이가 $1.2\,\Delta r$ 로 0.15(거의 동점 폭 0.1의 1.5배) 넘게 크고, 그 단조 근음 위 m7이 구간 코드 시간의 1/3 이상일 때만 그 m7을 나란한 장조의 으뜸화음으로도 세어 다시 비교합니다. 다른 장조로는 옮기지 않습니다. 조건 없이 쓰면 GTZAN에서 맞던 조 29곡이 틀리게 바뀌어(틀리던 26곡이 맞게), 조건을 좁혔습니다.
$[\,c \subset K\,]$ 는 코드 구성음이 모두 조성 음계(단조는 이끎음 포함)에 드는지, $\mathrm{I}K$ 는 으뜸화음(장조 I·Imaj7, 단조 i·im7), $N{V \to I}$ 는 V·V7 → I 진행 수, $N_\text{phrase}$ 는 구간의 첫 온마디부터 4마디마다의 첫 박 수입니다. 마지막 항의 비중 1.2는 조성이 분명한 곡 18개로 정했습니다.
전조 이름 다듬기(1.10.3). 곡 첫 조성 구간이 8마디 이하이고 다음 구간과 같은 갈래(장·단)의 버금딸림·딸림 조이면 다음 조에 합칩니다. 도입부가 iv나 V에서 시작하면 악구 첫 코드 때문에 도입부만 다른 조로 정해져 “B♭ 단조 → F 단조”처럼 거짓 전조가 적히기 때문입니다. 또 전조가 있는 곡은 조 이름을 가장 긴 구간의 갈래로 통일합니다. 나란한 장·단조는 구성음이 같아 이름만의 선택인데, 구간마다 따로 고르면 한 온음 위로의 전조가 “C 단조 → F 장조”처럼 적혀 무엇이 바뀌었는지 가려지므로 “C 단조 → D 단조”로 적습니다.
전조 경계 다듬기(1.10.4). 전조 경계는 음 분포 창으로 정해서, 두 조에 걸친 음이 많은 코드(앞 조의 V7 등) 때문에 새 조 쪽으로 한두 마디 일찍 오기 쉽습니다. 코드를 다 정한 뒤, 경계 뒤 2마디 안의 마디 첫 박마다 ‘코드 구성음이 앞 조 음계에 드는 비율 − 새 조 음계에 드는 비율’을 박마다 더해, 앞 조 코드를 가장 많이 앞 조에 남기는 마디 첫 박으로 경계를 미룹니다(0.5박 넘게 나을 때만). 앞으로는 옮기지 않습니다. 새 조의 딸림화음을 거쳐 으뜸화음부터 새 조로 둔 자리와 부딪히지 않게 하기 위해서입니다. 또 새 조의 첫 박이 N.C.이고 그 마디의 절반 이상이 N.C.(브레이크)이며 새 조의 으뜸화음이 아직 나오지 않았으면, 그 마디의 소리는 다음 악절의 못갖춘 시작으로 보고 다음 마디 첫 박부터 새 조로 적습니다(1.10.5에서 조건을 좁힘: 짧게 쉬었다가 바로 새 으뜸화음을 치는 마디는 그 마디부터 새 조). 「夜に駆ける」의 B 단조는 102마디(C 단조의 G7)에서 103마디(첫 반 마디가 무음이고 반주 없는 노래 시작음만 있는 브레이크)를 거쳐, 반주가 B 단조로 들어오는 104마디로 옮겨졌습니다. 「고민중독」의 A♭ 장조 전조는 2마디 이른 곳에서 1마디 이른 곳(전조로 넘어가는 경과 마디)으로 옮겨졌습니다.
카멜롯 휠은 조성을 5도권 순서로 1~12번에 놓고 장조에 B, 단조에 A를 붙입니다. 장조 으뜸음 $t$ (C = 0)의 번호와, 나란한 장조(으뜸음 $t+3$)와 번호를 공유하는 단조의 번호는 다음과 같습니다.
n_\text{major}(t) = \big((7t + 7) \bmod 12\big) + 1,\qquad n_\text{minor}(t) = n_\text{major}\big((t + 3) \bmod 12\big)
$7t \bmod 12$ 는 C에서 5도를 몇 번 올라간 자리인지를 뜻합니다( $7 \cdot 7 \equiv 1 \pmod{12}$ 이므로 5도 위의 조성은 번호가 1 커짐). 여기에 7을 더해 C 장조가 8B가 되게 합니다.
| 번호 | A (단조) | B (장조) |
|---|---|---|
| 1 | A♭m (G♯m) | B |
| 2 | E♭m (D♯m) | F♯ (G♭) |
| 3 | B♭m | D♭ |
| 4 | Fm | A♭ |
| 5 | Cm | E♭ |
| 6 | Gm | B♭ |
| 7 | Dm | F |
| 8 | Am | C |
| 9 | Em | G |
| 10 | Bm | D |
| 11 | F♯m | A |
| 12 | C♯m (D♭m) | E |
코드는 근음이 같고 3음이 같은 조성의 번호를 쓰고, 코드 종류는 첨자로 남깁니다. 3음이 없는 sus 코드는 B 쪽에 둡니다.
| 코드 종류 | 카멜롯 부호 | 예 |
|---|---|---|
| 장3화음, 7, maj7, aug, sus2, sus4 | 근음 장조의 번호 + B | C → 8B, G7 → 9B7, Fmaj7 → 7Bmaj7, Esus4 → 12Bsus4 |
| 단3화음, m7, dim, m7♭5, dim7 | 근음 단조의 번호 + A | Am → 8A, Dm7 → 7A7, Bdim → 10Adim, Bm7♭5 → 10A7♭5 |
장조 $n$B의 다이아토닉 코드는 I·IV·V가 $n$B·$(n-1)$B·$(n+1)$B, ii·iii·vi가 $(n-1)$A·$(n+1)$A·$n$A로, 모두 $n \pm 1$ 안에 모입니다(12 다음은 1).
그 자리의 조성(으뜸음 $t$, 장·단조)을 기준으로 근음 $r$ 의 음정 $d = (r - t) \bmod 12$ 를 도수로 바꿉니다. 단조는 자연단음계를 기준으로 하고, 이끎음 위의 감화음은 관례대로 임시표 없이 vii°로 적습니다.
| $d$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 장조 | I | ♭II | II | ♭III | III | IV | ♯IV | V | ♭VI | VI | ♭VII | VII |
| 단조 | I | ♭II | II | III | ♯III | IV | ♯IV | V | VI | ♯VI | VII | ♯VII |
| 규칙 | 내용 |
|---|---|
| 대·소문자 | 3음이 단3도인 코드(m, m7, dim, m7♭5, dim7)는 소문자 |
| 성질 기호 | dim·dim7 → °, m7♭5 → ø, aug → +, maj7 → M |
| 자리바꿈 숫자 | 3화음은 기본형(없음), 6, ⁶₄. 7화음은 7, ⁶₅, ⁴₃, ⁴₂. 베이스음 표기를 끄면 기본형 |
| 부속 딸림화음 | 조성 밖의 장3화음·딸림7화음이 바로 다음 코드로 완전5도 내려가 해결하고, 그 코드가 으뜸화음이 아닌 조성 안 3화음 x이면 V/x, V7/x. 단조의 VII → III 도 V/III |
| 부속 이끎음 화음 | 조성 밖 감화음이 반음 위의 조성 안 3화음 x로 가면 vii°/x, vii°7/x, viiø7/x |
| 부속 ii | 조성 밖 단3화음·m7·m7♭5가 완전5도 아래의 V/x로 이어지면 ii/x, ii7/x, iiø7/x |
| 전조 직전 | 다음 구간 조성의 V·V7이 그 조성의 으뜸화음으로 가면 새 조성 기준 V |
템포 도약. 8초 창(1초 간격)마다 전체 박 주기의 0.6~1.65배 범위에서 박 주기 점수 $s_w(\ell)$ 를 구하고 템포 상태 비터비로 흐름을 고릅니다. 원래 템포 근처(±4%)의 점수는 창 최댓값의 60% 이상이면 1로 포화해 쉽게 떠나지 않게 하고, 다른 템포는 실제 점수로 비교해 2/3배 같은 가짜 주기로 가지 않게 합니다. 창 사이 이동 비용은 다음과 같습니다.
c(d) = \begin{cases} 0 & d \le 0.005 \\ -6 - 100\,(d - 0.005) & 0.005 < d \le 0.04 \\ -12 - 2d & d > 0.04 \end{cases},\qquad d = \left|\ln\frac{\ell_w}{\ell_{w-1}}\right|
서서히 바뀌는 템포는 창마다 0.5% 이내로 비용 없이 따라가고, 작은 걸음을 이어 멀리 가는 길은 한 번의 도약보다 비싸집니다. 경로에 4% 넘는 도약이 없으면 (6)의 국소 템포 곡선을 그대로 쓰고, 있으면 도약 지점으로 구간을 나눠 구간마다 같은 방식(구간 중심 템포 ±15%, 7창 중앙값)으로 곡선을 만듭니다.
워킹 베이스 층위. 템포가 85 BPM 이하인데 저역 온셋의 박 위/박 사이 비가 2.5 이하이고 백비트(킥 반대쪽에 몰린 스네어, $s_{S_n} \le -0.15$)가 없으면 한 층위 위로 올리고, 이렇게 올린 곡은 150 BPM 이하에서 한 번 더 확인합니다. 템포 후보는 40~300 BPM입니다.
박자 후보. 단박자 곡은 4·3·5·7박을 비교합니다. 4박이 아닌 박자는 첫 박 대비도가 4박보다 (3박은 0.25, 5·7박은 0.3) 넘게 높고, 길이를 억지로 늘이거나 줄여 맞춘 마디가 15% 이하일 때만 고릅니다. 4박 경로에서 셋째 박의 첫 박 증거가 첫째 박의 85% 이상이면 2박으로 봅니다.
파워 코드. 파워 코드 상태의 점수에서, 근음 위 2·3·4·5반음 크로마의 합이 근음과 5음의 0.7배를 넘는 만큼 감점합니다.
e_s(c_5) \leftarrow e_s(c_5) - 6\,\max\Big(0,\ \sum_{d \in \{2,3,4,5\}} o_s(r+d) - 0.7\,\big(o_s(r) + o_s(r+7)\big)\Big)
또 곡 안에서 파워 코드가 16박 이상, 전체의 8% 이상일 때만 적고, 그보다 적으면 파워 코드를 빼고 다시 판정합니다. 발라드의 sus4가 드문드문 파워 코드로 읽히는 것을 막기 위해서입니다.
온음 위 베이스. 장3화음 위에 근음보다 온음 높은 베이스를 둔 상태(F/G, B♭/C, 사전 점수 −1.6)를 두고, 슬롯 베이스 크로마의 30% 이상이 그 음일 때만 인정합니다(아니면 −3). 로마 숫자는 베이스를 근음으로 보아 V11, 카멜롯은 베이스 조성 번호에 11을 붙입니다.
자리바꿈 다듬기. 같은 코드 안에서 베이스가 세 번 이상 움직이면(워킹 베이스) 2박보다 짧은 자리바꿈을 앞뒤에 합치고, 5음 베이스는 근음과 오가는 교대 베이스로 보아 합칩니다(구간 전체가 5음 베이스면 남김). 발라드의 한 박짜리 하행 베이스(V⁴₂ → iii7)는 그대로 둡니다.
박자 변화. 첫 박 모델의 상태를 (박자, 마디 안 위치)로 넓혀, 마디가 끝날 때만 아주 작은 확률 $\mu = 10^{-7}$ 으로 다른 박자(3·4·5·7박)로 넘어가게 합니다(1.10.0에서 $10^{-6}$ → $10^{-7}$ : 곡 끝맺음에서 코드가 한 박씩 앞서 바뀌는 곳을 3/4로 읽지 않게). 새 박자가 4마디 이상 이어질 때만 박자 변화로 보고(한두 마디짜리는 불규칙 마디), 그런 변화가 없으면 이 모델의 결과를 쓰지 않아 박자가 일정한 곡의 결과는 바뀌지 않습니다.
박자 변화 확인(1.11.4). 첫 박 증거가 흐트러진 구간(윗소리가 길게 이어지고 화음은 베이스로 움직이는 후렴 등)에서는 잡음이 다른 마디 길이로 우연히 맞아떨어져, 끝까지 4/4인 곡의 끝 후렴이 5/4·7/4로 적힐 수 있습니다. 그래서 바뀐 박자 구간마다 박별 트레블·베이스 크로마가 마디 길이 $m$ 의 악구 간격(4마디·8마디) 떨어진 박끼리 얼마나 비슷한지 $S(m) = \max_{k \in {4, 8}} \overline{\cos}(i,\ i + k m)$ 를 재서, 주 박자의 $S$ 가 새 박자의 $S$ 보다 0.02 넘게 높으면 그 박자 변화는 빼고 다시 풉니다. 대중음악은 4·8마디 악구로 되풀이되어 한두 마디 간격은 박자를 가르지 못하기 때문입니다. 8마디 악구 되풀이를 두 번 볼 수 있도록 구간이 주 박자로 16마디 이상일 때만 씁니다(30초 조각에서는 구간이 짧아 되풀이 평균이 흔들려 첫 박이 나빠지는 곡이 있었음).
템포 표시. 박마다 앞뒤 4박 간격의 중앙값으로 템포를 구해 재생 중 화면 위 BPM에 씁니다. 곡 첫 박에는 처음 16박의 템포를, 그 밖에는 앞뒤 8박의 템포가 12% 넘게 갑자기 바뀌고 새 템포(뒤 16박)가 직전 표시보다 8% 넘게 다른 곳에 표시를 붙입니다. 표시 위치는 차이가 최댓값의 절반을 넘기 시작한 박이며, 두 박 안에 마디 첫 박이 있으면 그리로 맞춥니다.
서서히 바뀌는 템포. 앞뒤 8박 간격의 중앙값으로 다듬은 템포가 4박 전보다 1% 넘게 한 방향으로 계속 바뀌고, 모두 합쳐 8% 넘게 16박 이상에 걸쳐 바뀐 구간을 찾습니다(갑자기 바뀐 곳이 들어 있는 구간은 제외). 다듬은 곡선은 곡 끝에서 늦게 따라가므로, 구간 끝 뒤 16박 안에서 박마다 템포가 1% 넘게 더 바뀌면 거기까지를 끝으로 봅니다. 시작을 4박 앞으로, 끝을 뒤로 늘려 잡으므로 이웃 구간이 겹칠 수 있는데, 같은 방향으로 겹치거나 맞닿으면 한 구간으로 합치고, 반대 방향으로 겹치면 겹친 박 안의 템포 극점에서 나눈 뒤 위 조건(16박, 8%, 갑자기 바뀐 곳 없음)을 다시 확인합니다(1.10.0). 차트에는 구간 시작 박부터 끝 박 바로 앞까지 육각형 틀을 걸치고, 끝 박에는 도착한 템포를 흰 알약으로 둡니다. 재생 중에는 틀이 재생 위치까지 채워지고, 도착 박에 닿으면 채움이 0.8초 동안 $(1-u)^2$ 로 옅어집니다($u$ 는 도착 뒤 지난 시간 ÷ 0.8초). 도착 알약은 도착 박에서 다음 시간 동안 켜졌다가 바로 꺼집니다($\Delta b$ 는 도착 박의 길이).
T_{\mathrm{on}} = 1.1\,\Delta b + 0.06\,\mathrm{s}
1.1배는 다음 박으로 번지는 정도를 템포에 비례하게, 0.06초는 꺼지는 순간이 다음 박의 시작과 겹쳐 보이지 않게 하는 고정 여운입니다. 100 BPM에서 여운이 0.12초이고, 느린 곡일수록 조금 길고 빠른 곡일수록 조금 짧습니다.
r_j = \left|\ln\frac{\mathrm{med}\{\Delta b_{j-8}, \dots, \Delta b_{j-1}\}}{\mathrm{med}\{\Delta b_{j}, \dots, \Delta b_{j+7}\}}\right| \ge 0.12
이어짐(회색). 마디 첫 박의 코드가 앞 칸과 같을 때, 그 코드 구성음의 세기를 박 직전 약 0.12초 평균과 직후 약 0.12초 최대로 비교합니다(트레블 크로마는 구성음 전체, 베이스 크로마는 베이스음). 어느 쪽이든 1.2배 넘게 뛰면 새로 친 것으로 보고 검은 글자로, 아니면 끌어 둔 것으로 보고 회색으로 적습니다.
r = \frac{\max_{t \le \tau \le t + 0.12}\ \sum_{k \in \mathcal{C}} c_\tau(k)}{\operatorname{mean}_{t - 0.12 \le \tau < t}\ \sum_{k \in \mathcal{C}} c_\tau(k) + \epsilon} \ge 1.2
약박 바뀜 다듬기. 짝수 박자의 약박(2·4박)에서 코드가 바뀐 것으로 판정됐는데, 바로 앞 강박에서 옛 코드에만 있는 음이 새로 쳐지지 않았고(위 비가 1.3 미만) 그 강박에서 새 코드 점수가 옛 코드보다 2.5 이상 낮지 않으면, 바뀜을 앞 강박으로 옮깁니다. 분산화음 반주에서 Esus4 → E의 3음(G♯)이 4박에야 울려 한 박 늦게 판정되던 것을, 악보 관례대로 3박에 적기 위해서입니다. 마디 첫 박 경계와, 옛 코드가 다시 쳐졌거나 그대로 울리는 경계는 옮기지 않습니다.
마디 첫 박 한 박 코드(1.11.8). 최종 HMM은 마디 안에서 코드를 바꾸는 데 벌점(둘째 박 4.0)을 매기므로, 마디 첫 박에만 울리는 한 박짜리 코드는 그 박에서 점수가 앞서도 뒤 박의 코드에 합쳐질 수 있습니다. 그래서 마디 첫 박부터 같은 코드 $Y$ 가 적혔을 때(앞 마디에서 이어 온 코드가 아니고 둘째 박도 같은 코드), 첫 박만 보면 가장 잘 맞는 다른 근음의 코드 $Z$ 를 찾아 다음을 모두 만족하면 첫 박을 $Z$ 로 적습니다. $E_b$ 는 박 $b$ 의 코드 점수, $r$ 은 위 이어짐 판정의 세기 비(박 직전 평균 대비 직후 최대)이고, $\mathcal{C}_Z \setminus \mathcal{C}_Y$ 는 $Z$ 에만 있는 음입니다.
E_b(Z) - E_b(Y) \ge 3.5,\qquad
E_{b+1}(Y) > E_{b+1}(Z),\qquad
r_b(\mathcal{C}_Z \setminus \mathcal{C}_Y) \ge 1.2,\qquad
r_{b+1}(\mathcal{C}_Y \setminus \mathcal{C}_Z) \ge 1.2,\qquad
\mathrm{bass}(Z) \ne \mathrm{bass}(Y)
첫째·둘째 조건은 첫 박에서는 $Z$, 둘째 박에서는 $Y$ 가 낫다는 것이고, 셋째·넷째 조건은 두 코드를 정말 따로 쳤다는 근거(첫 박에 $Z$ 에만 있는 음이 새로 울리고, 둘째 박에 $Y$ 에만 있는 음을 다시 침)입니다. 점수 조건만 두면 앞 마디 코드의 잔향이 새 코드와 섞여 첫 박에서 다른 코드처럼 보이는 경우까지 바뀌는데, GuitarSet 정답으로 확인해 보니 그런 경우가 더 흔했습니다(4-6의 1.11.8 검증).
코드 이름 다듬기(1.10.0). 조성을 확정한 뒤, 경계는 그대로 두고 이름만 다듬습니다. 조성 안의 m7이 3음을 베이스로 둔 채 이어지면(Am7/C, Em7/G, Bm7/D) 같은 음으로 된 베이스 위 6화음(C6, G6, D6)이므로, 악보 관례대로 베이스 위 장3화음(C, G, D)으로 적습니다. 베이스 크로마에서 그 음이 구간 합으로 가장 셀 때만 바꾸고, 바로 앞뒤에 같은 m7이 다른 베이스로 붙어 있으면(Gm7/B♭ → Gm7) 한 코드 안에서 베이스만 움직인 것이라 그대로 둡니다. 베이스를 잘못 들었을 수 있는 곳도 그대로 둡니다. 그 음이 구간 베이스의 절반 미만이면서 박마다 가장 센 음인 칸이 3/4에 못 미치면(여러 음이 섞여 흐릿함) 바꾸지 않고, 같은 코드 사이에 잠깐 낀 m7(Em7 – F♯m7/A – Em7)도 스쳐 가는 화음이라 바꾸지 않습니다. sus4는 구간의 트레블 크로마에서 3음(장·단 중 큰 쪽)이 4음의 0.7배 이상 울리면 같은 근음의 3화음으로 적습니다. 선율이나 음정 있는 드럼에 섞인 4음을 sus4로 읽지 않기 위해서입니다. 정답이 sus4인 알려진 구간(합성 곡, GuitarSet 연주 정답, 실제 녹음)에서 이 비는 0.65를 넘지 않았습니다.
마디 끝 당김 다듬기(1.10.3). 마디의 마지막 박에서 바뀐 것으로 판정된 코드가 다음 마디 첫 박까지 같은 근음으로 이어지면, 앞당겨 친 당김(8분음표 앞에서 치는 스트로크)이나 베이스 접근음인 경우가 많습니다. 악보는 이런 바뀜을 다음 첫 박에 적으므로, 그 박에서 새 코드 점수가 옛 코드보다 6.5 넘게 앞서지 않으면 바뀜을 다음 첫 박으로 옮깁니다. 문턱은 실제 녹음 4곡의 후보 11곳을 정렬한 코드 차트와 맞춰 본 값입니다(옮긴 7곳 중 6곳이 차트와 같고, 정말 그 박에 바뀐 2곳은 그대로 둠).
소리는 공기 압력의 떨림이고, 음높이는 떨림의 주파수입니다. 12평균율은 한 옥타브(주파수 2배)를 12개의 같은 비로 나눕니다.
f(m) = 440 \cdot 2^{(m - 69)/12}\ \text{Hz},\qquad
m(f) = 69 + 12\log_2\,\frac{f}{440},\qquad
\text{cent}(f_1, f_2) = 1200\log_2\,\frac{f_1}{f_2}
옥타브가 주파수 2배라는 사실이 음들을 12칸(크로마)으로 접는 근거입니다. 녹음의 기준음은 오래된 녹음의 테이프 속도, 악기 조율, 의도적인 튜닝 때문에 440 Hz에서 벗어나곤 합니다. 예를 들어 +18 cent면 $440 \cdot 2^{0.18/12} \approx 444.6$ Hz입니다.
현이나 관의 진동은 기본 주파수 $f_0$ 의 정수배 $h f_0$ 성분(배음)을 함께 냅니다. 배음이 기본음으로부터 떨어진 거리는 $12\log_2\, h$ 반음입니다.
| 배음 $h$ | 반음 거리 | 가장 가까운 평균율 | 차이 | 템플릿 가중치 |
|---|---|---|---|---|
| 1 | 0 | 0 (같은 음) | 0 cent | 1 |
| 2 | 12.00 | 12 (옥타브) | 0 cent | 0.6 |
| 3 | 19.02 | 19 (옥타브 위 5도) | +2.0 cent | 0.36 |
| 4 | 24.00 | 24 (두 옥타브) | 0 cent | 0.216 |
| 5 | 27.86 | 28 (두 옥타브 위 장3도) | −13.7 cent | 0.13 |
| 6 | 31.02 | 31 (두 옥타브 위 5도) | +2.0 cent | 0.078 |
| 7 | 33.69 | 34 | −31.2 cent | 쓰지 않음 |
7번 배음은 평균율에서 31 cent나 벗어나 다른 음 칸에 잘못 들어가므로 템플릿에서 뺐습니다. 배음을 모델에 넣는 효과는 큽니다. C 장3화음(C, E, G)만 쳐도 E의 3배음은 B, G의 3배음은 D가 되어, 배음을 무시하면 Cmaj7이나 Cadd9처럼 보입니다. 단3화음 Cm에서는 C의 5배음이 E(장3도)를 만들어 장조 쪽으로 기울어 보입니다. 템플릿이 이 성분까지 품고 있어야 비교가 공정해집니다.
| 소리 | 주요 대역 | 물리적 특징 | 분석에서의 쓰임 |
|---|---|---|---|
| 킥 드럼 | 약 40–100 Hz | 짧은 저음 붐, 음높이가 빠르게 내려감 | 저역 플럭스 → 박, 첫 박 증거 |
| 베이스 | 약 41 Hz(E1)–400 Hz | 정수배 배음, 긴 지속 | 베이스 크로마 → 전위(슬래시 코드) |
| 스네어 | 몸통 약 150–250 Hz, 줄 잡음 약 2–10 kHz | 넓은 대역에 걸친 잡음 | 중역과 고역의 동시 상승 → 백비트 |
| 하이햇 | 약 5–15 kHz | 금속성 잡음 | 고역 플럭스(박 판정에선 가중치를 낮춤) |
| 피아노, 기타, 보컬 | 기본음 약 80 Hz–1 kHz, 배음 수 kHz | 정수배 배음 | 트레블 크로마 → 코드 |
창 길이 $T$ 의 주파수 분해능은 대략 $1/T$ 이고, 이론적 하한은 $\Delta t\,\Delta f \ge 1/(4\pi)$ 입니다(가우스 창에서 등호). 반면 반음 간격은 주파수에 비례합니다.
\Delta f_\text{semitone}(f) = f\,(2^{1/12} - 1) \approx 0.0595\,f
베이스 최저음 E1(41.2 Hz)에서 반음 간격은 2.45 Hz뿐이라, 구분하려면 $T \gtrsim 1/2.45 \approx 0.41$ 초가 필요합니다. A2(110 Hz)에서는 6.5 Hz, A4(440 Hz)에서는 26.2 Hz로 넉넉해집니다. 그래서 용도마다 창을 따로 씁니다.
| 용도 | 샘플레이트 | 창 | 창 길이 | 홉 | 칸 간격 |
|---|---|---|---|---|---|
| 리듬(온셋) | 22,050 Hz | 1024 | 46.4 ms | 11.6 ms | 21.5 Hz |
| 트레블(코드) | 11,025 Hz | 2048 | 185.8 ms | 23.2 ms | 5.38 Hz |
| 베이스, 튜닝 | 11,025 Hz | 4096 | 371.5 ms | 46.4 ms | 2.69 Hz |
베이스 창은 0.41초에 조금 못 미치지만, 한 번에 울리는 베이스 음은 보통 하나이므로 봉우리 위치를 로그 포물선 보간으로 칸 간격보다 훨씬 정밀하게 추정해 보완합니다.
샘플레이트 $f_s$ 로는 $f_s/2$ (나이퀴스트 주파수)보다 높은 성분을 담을 수 없고, 그대로 줄이면 높은 성분이 낮은 주파수로 접혀 들어옵니다(앨리어싱). 그래서 리샘플러는 새 나이퀴스트의 92%에서 자르는 저역 필터를 겸합니다. 22,050 Hz 신호는 약 10.1 kHz까지 남아 하이햇 대역(2.5–10 kHz)을 담고, 11,025 Hz 신호는 약 5.07 kHz까지 남아 트레블 피크 범위(4.2 kHz까지)를 담습니다. 필요한 정보가 이 아래에 있으므로, 샘플 수를 줄여 계산량을 크게 낮출 수 있습니다.
| 스펙트럼을 $\ln(1 + \lambda | X | )$ 로 압축하면 절대 크기보다 상대 변화가 드러나, 조용한 기타 스트로크와 큰 킥이 모두 온셋으로 잡힙니다. |
코드 연주의 피아노 합성이 흉내 내는 물리 현상입니다.
| 현상 | 물리 | 합성에서 |
|---|---|---|
| 비조화성 | 강철 현의 굽힘 강성 때문에 높은 배음일수록 정수배보다 높아짐. $B = \pi^3 E d^4 / (64 T L^2)$ ( $E$ 영률, $d$ 지름, $T$ 장력, $L$ 길이) | $f_k = k f_0\sqrt{1 + Bk^2}$, 높은 음역일수록 $B$ 증가 |
| 타격 위치 | 해머가 현 길이의 약 1/7~1/9 지점을 때리면, 그 지점이 마디(움직이지 않는 점)인 배음은 거의 울리지 않음 | $\lvert\sin(\pi k x_h)\rvert$ , $x_h = 1/7.5$ |
| 해머 펠트 | 부드러운 펠트가 짧게 닿아 아주 높은 성분을 걸러 냄 | $1/(1 + (f_k/f_b)^2)$ |
| 이중 감쇠 | 한 음의 여러 현이 브리지를 통해 에너지를 주고받아, 처음엔 빨리 줄고 나중엔 오래 남음 | 즉음 60%, 여음 40% |
| 맥놀이 | 한 음의 현들이 1 cent 안팎으로 조금씩 다르게 조율되어 소리가 천천히 일렁임 | 낮은 배음을 현 2~3개로 합성하고, 같은 배음의 현들은 같은 위상에서 출발시켜 서서히 어긋나게 함 |
| 배음별 감쇠 | 높은 배음일수록 에너지를 빨리 잃어 시간이 갈수록 소리가 어두워짐 | $\tau_k \propto (f_0/f_k)^{0.7}$ |
| 음역별 지속 | 낮은 현은 길고 무거워 오래 울림 | 기본음 여음 1.0~8.5초 |
outputLatency 로 보정합니다.flowchart LR
subgraph UI["UI 스크립트"]
LD["불러오기<br/>파일 선택, 끌어 놓기, 디코딩"]
CT["보정, 설정 컨트롤"]
RD["렌더러<br/>코드 차트, 표시등, 파형"]
PL["재생기와 스케줄러"]
PN["코드 연주<br/>보이싱, 피아노 합성 캐시"]
end
subgraph EN["엔진 스크립트 Engine"]
AN["analyze"]
BT["buildTimeline"]
TX["chartText"]
end
LD -->|PCM 채널| AN
AN -->|분석 결과 A| BT
CT -->|설정| BT
BT -->|타임라인 TL| RD
BT -->|비트, 첫 박| PL
BT -->|코드 구간| PN
BT --> TX
PL --> AO["리미터, AudioContext 출력"]
PN --> AO
.
├── chordity.html 앱 전체: 엔진 스크립트 + UI 스크립트 + 스타일 (단일 파일, 약 230 KB)
├── information.html 도움말: 판단 근거, 분석 원리와 수식, 검증 결과 (단일 파일)
├── LICENSE 이용 조건
├── README.md
├── README.{en,ja,zh,fr,es}.md README 번역: English, 日本語, 中文, Français, Español
└── docs/
├── screenshot-desktop.png
├── screenshot-mobile.png
├── notation-name.png
├── notation-camelot.png
└── notation-roman.png
chordity.html 의 첫 번째 <script> 가 전역 Engine 을 정의합니다(Node에서 불러 쓸 때는 module.exports 로도 내보냄). 아래는 이 프로젝트를 개발하고 검증하는 데 쓰는 설명입니다. 엔진을 떼어 내 자기 프로그램에서 불러 쓰는 것은 수정·일부 사용에 해당해 원작자의 허락 없이는 할 수 없습니다(LICENSE 2번 가).
// buf: AudioBuffer (예: AudioContext.decodeAudioData 결과)
const channels = Array.from({ length: buf.numberOfChannels }, (_, c) => buf.getChannelData(c));
// 무거운 단계: 한 번만
const A = await Engine.analyze(
{ channels, sampleRate: buf.sampleRate },
(progress, stage) => console.log(Math.round(progress * 100) + '%', stage ?? '')
);
// 가벼운 단계: 설정이 바뀔 때마다
const TL = Engine.buildTimeline(A, { vocab: 'extended', res: 2, slash: true });
console.log(Engine.chartText(TL, { title: '내 노래' }));
// # 내 노래 | BPM 97.1 | 박자 4/4 | 조성 C 장조
// 1 | C . / . / . / . | G . / . / . / . | ...
// 카멜롯 표기로 내보내기: 조성은 C 장조(8B), 코드는 8B, 9A7 처럼
console.log(Engine.chartText(TL, { title: '내 노래', notation: 'camelot' }));
// 로마 숫자로 내보내기: I, V7/vi, ii65 처럼(자리바꿈 숫자는 이어서 적음)
console.log(Engine.chartText(TL, { title: '내 노래', notation: 'roman' }));
설정 (Engine.defaultSettings())
| 키 | 값 | 기본값 | 뜻 |
|---|---|---|---|
ops |
'half', 'x2', 'd2', 'd3' 의 배열 |
[] |
비트 보정 연산을 순서대로 적용 |
meter |
'auto' 또는 정수 |
'auto' |
마디당 박 수 |
downShift |
정수 | 0 |
첫 박 이동(양수면 늦춤) |
vocab |
'basic', 'standard', 'extended' |
'standard' |
코드 어휘 |
res |
1 또는 2 |
1 |
코드 변화 단위(1박, ½박) |
slash |
참, 거짓 | true |
베이스음(전위) 표기 |
분석 결과 A 의 주요 필드
| 필드 | 내용 |
|---|---|
duration, sampleRate |
원본 길이(초), 원본 샘플레이트 |
fl, fm, fh, rms, O |
저·중·고역 플럭스, 음량, 온셋 포락선 (리듬 프레임 단위) |
nov, S, drum |
화성 변화, 박 강세, 킥·스네어 곡선 |
audible, startT, endT |
소리 구간 |
harm |
tuning(반음), treble, bass 크로마와 각각의 홉 |
tempo |
bpm, tau, 상위 후보 cands |
beats |
자동 비트 시각(초) |
phase, level |
위상 검증, 박 단계 검증의 판정값과 근거 |
level43 |
4:3·3:2 박 단계 확인: triple(박이 셋으로 쪼개져 보였는가), applied, 셋으로 보였으면 curBpm과 후보별 판정값 alts, 바꿨으면 ratio(4/3 = 느리게, 2/3 = 빠르게), toBpm, flip(반 박 옮김) |
elapsed |
분석 시간(초) |
타임라인 TL 의 주요 필드
| 필드 | 내용 |
|---|---|
empty |
비트가 부족해 차트를 만들지 못했는가 |
beats, pos, barOfBeat |
비트 시각, 마디 안 위치(0이 첫 박), 비트가 속한 마디 |
bars[] |
num, pickup, t0, t1, startBeat, nBeats, events[] |
events[] |
off, dur(박 단위), tied, pcs(구성음, 0 = C), bassPc(연주할 베이스), label(root, base, ext, bass, text, ascii, nc, cam: 카멜롯 code·ext·text·ascii, rn: 로마 숫자 acc·num·qual·fig·sus·sec·text·ascii) |
bpm |
비트 간격 중앙값으로 계산한 템포 |
meter |
m, triple, label(예: 4/4, 12/8), contrast, alt |
key |
첫 조성: tonic, mode, score, name(예: E♭ 장조) |
keys[] |
조성 구간: name, cam(카멜롯 부호), tonic, mode, startBeat, barNum(마디 번호), beat(몇 박), t0(초) |
| 시계 | 기준 | 쓰는 곳 |
|---|---|---|
| 샘플 시간 | 디코딩한 버퍼의 샘플 번호 ÷ 샘플레이트 | 분석, 모든 비트 시각(초) |
| 오디오 시계 | AudioContext.currentTime |
음악 재생과 클릭 예약 |
| 화면 시계 | requestAnimationFrame |
지금 코드 상자와 이름 확대, 표시등, 재생 위치 표시 |
비트 시각은 샘플 시간으로 계산되고, 4-1의 (15) 식으로 오디오 시계에 옮겨집니다. 재생 버퍼와 분석 신호가 같은 디코딩 결과에서 나오고 리샘플러 지연이 0이므로 두 시간축 사이에 오프셋이 없습니다.
지금 코드 상자와 이름 확대(1.10.1). 화면 시계의 매 프레임마다 재생 위치가 든 박과 그 박 안의 위치(박 길이에 대한 비)로 마디 안의 코드를 찾습니다. 재생 중 위치는 출력 지연을 빼되 재생을 시작한 곳보다 앞서지 않게 해, 위치를 옮긴 직후 그 앞 코드가 잠깐 비치지 않습니다. 코드 단위는 새로 친 코드(검은 글자, N.C.)와 그 뒤에 끌어 둔 회색 코드들이며, 마디를 넘어 이어질 수 있습니다. 단위가 바뀔 때만 화면을 고칩니다. 첫 이름은 0.1초 만에 커지고(CSS 전환, 떠날 때는 0.7초에 걸쳐 줄어듦), 배율은 1.3배(두 줄 마디는 1.15배)를 넘지 않으면서 다음 이름까지 남은 자리 ÷ 이름 폭보다 작게 정해 커진 이름이 이웃 이름에 닿지 않습니다. 커지는 축은 이름 왼쪽(박 자리 그대로), 높이는 한 줄 마디 45%(윗줄 알약과 재생 중 길어지는 빗금 사이), 두 줄 마디 위 끝입니다. 형광펜 상자는 차트 줄마다 하나이며, 단위가 걸친 줄마다 첫 이름 7px 앞(줄을 넘어 이어지면 줄 왼쪽)부터 다음 코드 이름 2px 앞(마디 끝이면 세로줄 바로 앞, 곡 끝은 겹세로줄 앞, 다음 줄로 이어지면 줄 오른쪽)까지 감쌉니다. 상자의 두 끝 $x$ 는 각각 감쇠 진동자 $\ddot x = \omega^2 (x^\ast - x) - 2\zeta\omega\dot x$ 를 따라 목표 $x^\ast$ 로 갑니다. 가는 쪽 끝은 $\omega = 24\ \mathrm{rad/s},\ \zeta = 0.42$ 로 목표를 약 23% 지나쳤다 돌아오고, 반대쪽 끝은 $\omega = 16\ \mathrm{rad/s},\ \zeta = 1$ (임계 감쇠)로 지나치지 않고 뒤따라 0.6초쯤에 멈춥니다. 따라가는 끝이 지나치지 않으므로 짧은 코드로 옮겨도 두 끝이 엇갈리지 않습니다. 새로 생기는 줄의 상자는 코드 시작에서 튀어나오고, 단위가 떠난 줄의 상자는 가는 쪽으로 오므라들며 옅어지다가 두 끝이 만나면 사라집니다. 곡 끝을 지나면 앞으로 오므라듭니다. 차트를 다시 그린 직후(표기·화면 폭 바꿈)와 움직임 줄이기 설정에서는 상자와 이름이 바로 제자리로 갑니다. 상자 안에는 진행 채움(주황)이 조각 왼쪽 끝부터 재생 위치까지 찹니다. 재생 위치의 x는 그 마디의 박 자리(빗금 자리)를 박 안 위치(박 길이에 대한 비)로 나눈 곳이고(1.10.2), 단위가 앞 줄에서 이어 왔으면 앞 줄 조각은 끝까지 찹니다. 채움은 상자 안 좌표로 그려 상자가 스프링으로 움직이는 동안에도 끝이 재생 위치에 머뭅니다. 상자 색은 밝은 테마 #ffed85, 어두운 테마 #4b4219, 채움은 밝은 테마 #ffc061, 어두운 테마 #5e3110이고, 회색 코드와 N.C.의 회색(밝게 #4e5662, 어둡게 #aab1bb)까지 채움·상자·차트 면 위 어디서나 4.5:1 이상으로 읽힙니다. 그래서 상자가 움직이는 동안이나 이름이 상자보다 긴 두 줄 마디에서도 글자색을 바꾸지 않습니다.
flowchart LR
M["음악 버퍼"] --> GM["음악 볼륨"]
C["클릭 버퍼"] --> GC["클릭 볼륨"]
P["피아노 음<br/>음마다 세기, 좌우 위치"] --> PB["피아노 버스"]
PB --> MK["보정 이득 +6.4 dB"]
PB --> RV["방 잔향 1.6초"] --> W["잔향 양 0.22"] --> MK
MK --> GP["코드 볼륨"]
GM --> L["리미터 −1 dBFS"]
GC --> L
GP --> L
L --> O["출력"]
메트로놈 켜기, 첫 박 강조, 코드 이름 표기 방식(음이름, 카멜롯, 로마 숫자), 코드 연주 켜기와 치는 때, 음악과 클릭과 코드 볼륨, 클릭 타이밍, 자동 스크롤, 테마, 코드 어휘, 해상도, 슬래시 표기를 localStorage 의 chordity-prefs-v1 에 저장(이전 이름의 저장값이 있으면 옮겨 옴)합니다. 저장할 수 없는 환경에서도 앱은 그대로 동작합니다.
개발하면서 부딪힌 문제와 해결책입니다. 수치는 합성 곡 회귀 테스트와 실제 브라우저 테스트(4-6)에서 확인했습니다.
| 이슈 | 원인 | 해결 | 결과 |
|---|---|---|---|
| 느린 곡이 두 배 템포로 잡혀 8분음표마다 클릭 | 촘촘한 8분 아르페지오와 110 BPM 선호 분포 | 드럼 층 짝·홀 비대칭 검사로 절반 교정 | 68 BPM 발라드: 133 → 67.1 BPM |
| 빠른 곡이 절반 템포로 잡힘 | 킥만 박으로 인식 | 박 위 킥, 박 사이 스네어 교대 검사로 두 배 교정 | 176 BPM 펑크: 88 → 176 BPM |
| 제 템포인 피아노 곡을 절반으로 망칠 위험 | 왼손 베이스가 1·3박에만 있어 짝·홀이 비대칭 | 절반 교정은 110 BPM 이상에서만 | 72 BPM 피아노 유지 |
| 실제 발라드가 두 배 템포로 잡혀 코드가 반 박마다 바뀜(「세월이 가면」: 135 대 68 BPM) | 하이햇과 건반이 엇박을 채워 짝·홀 세기 비가 1.59로 기준 2.4에 못 미침 | 킥과 스네어가 같은 쪽에 몰리는지 보는 조건 추가(제 박이면 백비트 때문에 반대쪽) | 68.1 BPM으로 교정, 합성 곡 판정은 모두 그대로 |
| 절반으로 고친 뒤 가짜 3박·2박 마디 | 8분음표 추적이 필인에서 한 칸 미끄러졌는데, 비트를 하나씩 걸러 내 그 뒤 전체가 영향받음 | 걸러 내지 않고 새 박 주기로 다시 추적 | 불규칙 마디 5 → 1 |
| 느린 곡에서 박이 싱코페이션·필인에 끌려 흔들림 | 간격 벌점(고정 100)이 퍼센트 기준이라 느린 곡에선 100 ms가 넘는 흔들림도 싸게 허용 | 국소 템포 곡선과 비교하고, 벌점을 절대 시간 기준으로(α = 2000·T²) | 구간별 직선 잔차 RMS 96~222 → 6~23 ms, 리타르단도 시험 곡 80/80 |
| 빠른 록이 절반 템포로, 그것도 박이 스네어(2·4박) 위에 잡혀 코드가 반 박씩 섞임(178 BPM 곡이 89 BPM) | 베이스의 8분음표가 저역을 채워 킥이 약하게 잡히고, 템포 점수가 두 층위에 비슷함 | 박 위 스네어·박 사이 킥 모양과 두 배 템포 후보의 세기로 두 배(뒷박형) | 177.9 BPM, 첫 박 F 0 → 0.95, 코드 근음 62% → 86% |
| 하프타임 느낌의 빠른 곡이 절반 템포(170 BPM 곡이 85 BPM) | 느린 박이 킥에 앉고 스네어가 박 사이에도 옴 | 하프타임형 두 배. 박 사이에 저음 타격이 거의 없는 드럼 없는 피아노 곡은 제외 | 170.1 BPM, 합성 피아노 곡 그대로 |
| 110~130 BPM 곡을 절반으로 줄임 | 짝·홀 세기 차만 보고 8분음표를 박으로 잡았다고 판정 | 박 사이 저음(16분음표 자리여야 함)과 백비트를 확인 | GTZAN 대중음악 8곡 바로잡음, 「세월이 가면」 68 BPM 그대로 |
| 4/4 곡을 7/4·5/4·3/4로 적음 | 30초 남짓한 곡에서 드문 큰 첫 박 흔적 몇 개만 골라 대비도가 부풂 | 첫 박 증거의 자기상관 확인, 드럼 백비트가 뚜렷하면 짝수 박자만 | GTZAN에서 홀수 박자로 적은 곡 221 → 104 |
| 당김 리프에서 마디가 한 박 밀리고 가짜 5박·3박 마디가 낌 | 첫 박 증거가 8분음표 앞서 바뀌는 코드를 따라감 | 드럼 짝·홀 사전(킥 1·3박, 스네어 2·4박) | 불규칙 마디 9 → 3(「고민중독」), GTZAN 대중음악 첫 박 F 0.557 → 0.608 |
| 곡 끝맺음이 3/4로 바뀌어 적힘 | 코드가 한 박씩 앞서 바뀌어 3박 간격처럼 보임 | 박자 변화 확률 $10^{-6}$ → $10^{-7}$ | 4/4 유지, 합성 곡 박자 변화 4곡 그대로 |
| Am7/C, Em7/G처럼 3음 베이스 m7이 많이 나옴 | 같은 음으로 된 6화음(C6, G6)을 m7 자리바꿈으로 읽음 | 베이스가 가장 센 그 음이면 베이스 위 3화음으로 | GuitarSet에서 바뀐 13곳 중 12곳이 연주 정답과 맞음 |
| 장조 곡 중간에 나란한 단조로 전조한 것처럼 적힘(G 장조 → E 단조) | vi(Em)로 자주 내려앉는 진행과 B7 → Em 진행이 많은 구간만 따로 단조로 판정 | 나란한 장·단조로 갈린 이웃 구간을 합쳐 다시 판정, 단조가 뚜렷이 앞설 때만 단조 | 「고민중독」 G 장조 → A♭ 장조(정답과 같음), 다른 시험 곡의 조성은 모두 그대로 |
| 3음이 울리는데 sus4로 적음 | 선율이나 음정 있는 드럼(스네어 부분음)에 섞인 4음 | 3음이 4음의 0.7배 이상이면 3화음 | 「고민중독」 sus 오답 10% → 2% |
| 레게처럼 스캥크가 강한 곡에서 두 층위 판정이 갈림 | 두 배 템포 해석이 음악적으로도 통용됨 | 자동으로 강제하지 않고 ÷2 제공 | 의도적 비해결 |
| 비트가 6~8 ms 이르게 찍힘 | 46 ms 창이 어택을 미리 포함해 플럭스 정점이 앞섬 | 6.5 ms 보정 상수 | 평균 오차 0 ± 2 ms |
| 7화음의 61%를 3화음으로 인식 | 7화음 사전 벌점이 과도 | $\beta$, $\gamma$, 벌점을 격자 탐색으로 재조정 | 7화음 누락 0, 3화음의 7화음 오인 0 |
| 전조가 있는 곡도 조성을 하나로만 표시하고, 전조 뒤 코드를 옛 조의 표기로 적음(E 장조 구간의 A♭m7 등) | 조성을 곡 전체에서 한 번만 추정 | 조표 단위 조성 HMM으로 구간을 나누고, 구간마다 ♯/♭ 표기와 조성 가산점을 따로 적용 | 「세월이 가면」 45마디 1박부터 E 장조, 이후 G♯m7·C♯7로 표기 |
| 코드가 분명한 가단조 진행(Am … E7)을 E 단조로 판정 | 음 분포와 조성 프로필의 상관만으로 조성을 정해, 조성 밖 음(F)을 충분히 벌하지 못함 | 인식된 코드로 구간 조성을 다시 정하고(음계 적합, 으뜸화음 시간, V → I, 처음·마지막 코드) 음 분포 상관과 섞음 | A 단조로 판정, 로마 숫자 i v6 VI III6 … |
| 짧은 구간이 나란한 단조로 기움(E 장조 → C♯ 단조) | 구간이 vi로 끝나고 V/vi → vi 가 있으면 코드 단서가 단조 쪽으로 기움 | 4마디 악구 첫 박의 으뜸화음 비율을 단서로 추가하고 음 분포 비중을 18곡 시험으로 정함 | 조성이 분명한 18곡 모두 맞음 |
| 곡 중간에 템포가 크게 바뀌면 후반 박을 놓치고 박자까지 틀림 | 국소 템포를 전체 템포 ±15% 안에서만 찾음 | 템포 상태 비터비로 도약을 찾고, 도약이 없는 곡은 이전 곡선을 그대로 씀 | 비트 64→93/96, 엇박 16→1 |
| 비밥을 한 마디에 한 번(65 BPM)으로 잡음 | 마디 단위 반복이 강해 템포 추정이 느린 층위를 고르고, 두 배 규칙은 백비트 스네어만 봄 | 템포 후보를 300 BPM까지, 박 사이 저음(워킹 베이스)으로 한 층위씩 올림 | 260 BPM, 비트 96/96 |
| 베이스가 C인데 Cm7을 E♭ 또는 E♭maj7로 적음 | 같은 음 구성(Cm7 = E♭6)에서 근음을 가르는 단서가 베이스뿐인데 가중치가 약함 | 베이스 가중치를 올려 보았으나 경과음 때문에 다른 곳에 C♯maj7/C 같은 오답이 생겨 되돌림 | 미해결, 한계에 기록 |
| 음악 시작 전에 가짜 못갖춘마디 | DP 경로가 무음까지 이어지고, 가장자리 정리의 이웃 평활이 이를 살려 둠 | 소리 구간 ±¼박 밖 비트 제거 후 외삽 | 제거 |
| 잡음 바닥이 소리로 판정 | 소리 구간 기준이 느슨함 | −40 dB → −38 dB | 제거 |
| 음정 있는 킥이 베이스 음으로 오인될 위험 | 킥의 저음 붐이 매 박 베이스 크로마에 쌓임 | 곡 전체 중앙값의 절반을 뺌 | 상시 성분 제거 |
| 무음 파일에서 가짜 비트 4개 | 소리 구간이 없을 때도 원래 경로를 유지 | 소리 구간이 없으면 비트 없음 | 빈 타임라인과 안내 문구 |
| ½박 이동 뒤 곡 시작 전에 클릭 | 중간점 계산이 맨 앞에 박을 덧붙임 | 소리 구간 이전 박은 버림 | 제거 |
| 곡 끝 잔향에 1박짜리 마디 | 외삽이 끝 경계만 확인 | 새 박 뒤로 반 박 이상 소리가 남을 때만 연장 | 제거 |
| 분석 중 탭을 바꾸면 크게 느려질 위험 | 백그라운드 탭에서는 setTimeout 이 1초 단위까지 늦춰질 수 있음 |
타이머 스로틀링 대상이 아닌 MessageChannel 로 제어권 양보 |
타이머 지연의 영향 없음 |
| → 키를 누르고 있으면 다음 마디로 안 넘어감 | 재생 예약(60 ms) 동안 위치가 이동 지점보다 앞으로 읽힘 | 예약 전 위치를 이동 지점으로 고정 | 연속 5회 → 정확히 5마디 |
| 재생, 일시정지 아이콘이 바뀌지 않음 | SVG 요소에는 .hidden 속성이 없음 |
hidden 특성을 직접 토글 |
정상 |
| 클릭 타이밍 슬라이더가 점으로 찌그러짐 | 체크박스용 CSS가 범위 입력에도 적용 | 선택자를 체크박스로 한정 | 정상 |
| 휴대폰에서 고정 재생 막대가 네 줄로 커짐 | 메트로놈 버튼과 볼륨이 줄바꿈된 채 모두 고정됨 | 메트로놈은 아이콘 버튼, 볼륨은 고정 영역 밖으로 | 두 줄(재생 줄과 파형), 높이 125 px |
| 분석과 재생의 시간축이 어긋날 위험 | 브라우저 리샘플러와 별도 디코딩의 지연 | 재생 버퍼를 직접, 지연 0으로 리샘플 | 오프셋 0(임펄스 시험 1.00000 s) |
| 피아노가 녹음보다 18 cent 낮게 울림 | 피아노를 A4 = 440 Hz로 합성 | 분석에서 추정한 기준음으로 합성하고, 기준음이 바뀌면 캐시를 다시 만듦 | 11개 음 모두 +17.7~+18.3 cent (정답 +18) |
| 음악, 클릭, 코드가 겹치면 디지털 클리핑 위험 | 세 소리의 합이 0 dBFS를 넘을 수 있음 | 출력 직전 리미터(−1 dBFS, 20:1, 어택 1 ms) | 셋에 같은 지연이라 정렬 유지, 클릭 시험 결과 변화 없음 |
| 코드 연주 소리가 너무 작음 | 피아노 타건은 첫 순간이 몸통보다 15~20 dB 커서, 피크에 맞춘 음량은 귀에 작게 들림(기본 볼륨에서 첫 1초 RMS −28 dBFS) | 피아노 경로에 고정 보정 이득을 두고, 배음별 시작 위상 난수로 첫 피크를 줄임 | 아래 행에서 최종 값 결정 |
| 보정 이득 +11.4 dB는 너무 큼 | 너무 작은 값(−28 dBFS)과 너무 큰 값(−18 dBFS) 사이에 적정점이 있음 | 두 값의 dB 중간으로 이분해 보정 이득 +6.4 dB | 이전보다 약 1.8~2.1배(+5.1~+6.5 dB). 최대 볼륨에서도 피아노 피크가 −3 dBFS 안팎이라 리미터가 음악을 누르지 않음 |
| 코드 연주의 울림이 짧음 | 즉음 비율이 높아 타건 직후 빠르게 작아짐 | 즉음 72 → 60%, 즉음 감쇠 시간 비 0.12 → 0.15, 여음 감쇠 시간 ×1.13, 음 길이 +0.8초 | 1–2초 꼬리 +2.7~+3.0 dB, 10 dB 감쇠 시간 1.3~1.8배 |
| 압축기로 음량을 올리면 울림이 약 3배로 길어짐 | 느리게 풀리는 압축기가 꼬리까지 끌어올림, 빠른 리미터는 첫 몇 ms 피크를 잡지 못함 | 압축기를 빼고 합성 파라미터와 고정 이득으로 조정 | 목표한 만큼(+3 dB)만 길어짐 |
| 위상을 현마다 무작위로 주면 울림이 과하게 길어짐 | 같은 위상에서 출발한 현들이 서서히 어긋나는 것이 이중 감쇠의 원인인데 이를 깨뜨림 | 배음마다 위상을 다르게, 같은 배음의 현들은 같은 위상 | 위상 변경만의 꼬리 변화 +5.9 → +0.6 dB |
| 항목 | 방법 | 효과 |
|---|---|---|
| 단계 분리 | 무거운 신호 처리는 한 번, 박자·코드 추론은 설정마다 | 보정 반영 약 40 ms(5분 곡) |
| 실수 FFT | $N/2$ 점 복소 FFT 한 번 + 후처리, 회전 인자와 비트 역순표 미리 계산 | FFT 계산량 약 절반 |
| 스펙트로그램을 저장하지 않음 | 리듬 플럭스는 3프레임 링 버퍼로 흘려 계산, 화성은 프레임당 상위 피크만 저장 | 4분 곡 기준 전체 스펙트로그램 세 벌(리듬, 트레블, 베이스 각 약 85 MB, 합계 약 254 MB) 대신 피크 약 4 MB |
| 폴리페이즈 리샘플러 | 커널을 위상 테이블로 미리 계산, 정수 누산기, 경계 검사 없는 빠른 경로 | 출력 샘플당 곱셈 약 48회(48 kHz → 22.05 kHz) |
| 누적합 | 음량 평준화와 피크 대비 판정의 구간 평균을 누적합으로 | 창 길이와 무관한 선형 시간 |
| O(T·K) 비터비 | “유지 또는 최댓값에서 전환” 구조 활용, 역추적은 Int16Array |
확장 어휘(K = 289)에서 O(T·K²) 대비 289배 적은 연산 |
| 협력적 멀티태스킹 | 32 ms마다 MessageChannel 로 양보 |
워커 없이 단일 파일 유지, UI 멈춤 없음 |
| 화면 갱신 최소화 | 마디, 박, 코드가 바뀔 때만 DOM 수정. 파형은 한 번 그린 두 캔버스를 CSS 폭으로 잘라 진행 표시 | 매 프레임 캔버스 재그리기 없음 |
| 클릭 소리 | 버퍼로 미리 만들어 재사용, 예약 목록으로 한꺼번에 취소 | 클릭마다 오실레이터 그래프를 만들지 않음 |
| 타입 배열 | Float32Array, Float64Array, Int16Array, Int32Array, Uint8Array |
메모리 연속성, JIT 최적화 |
| 피아노 합성 | 회전 재귀로 사인 생성(Math.sin 호출 없음), −100 dB 아래 계산 생략, 음마다 한 번만 합성해 캐시, 곡에 처음 나오는 순서로 미리 합성 |
재생 중 50 ms 넘는 메인 스레드 작업 0건 |
| 피아노 음 메모리 | 곡에 쓰이는 음만(시험 곡에서 15개) 2.6~5.6초 모노로 | 약 13 MB |
웹 워커를 쓰지 않은 이유도 있습니다. 워커는 별도 스크립트 파일이나 blob: URL이 필요해 단일 파일 배포와 엄격한 콘텐츠 보안 정책 환경에서 제약이 생깁니다. 32 ms 단위 양보만으로 5분 곡 분석 중에도 진행률 표시가 부드럽게 유지됩니다.
| 항목 | 방법 | 결과 |
|---|---|---|
| 실수 FFT | 256점 난수 신호를 직접 계산한 DFT와 비교 | 최대 오차 3.8×10⁻¹³ |
| 리샘플러 정확도 | 48 kHz 1 kHz 사인 → 22,050 Hz, 이상적 사인과 비교 | RMS 오차 7.7×10⁻⁶ |
| 리샘플러 지연 | 1.0초 위치의 임펄스를 변환한 뒤 정점 위치 확인 | 1.00000초 (지연 0) |
| 샘플레이트 | 22.05, 44.1, 96 kHz 입력 | 비트 적중 100%, 평균 오차 0.3 ms 이내 |
| 특수 입력 | 무음 5초, 1.5초, 0.2초 클립 | 오류 없이 빈 타임라인 |
정답(비트, 첫 박, 코드)을 알고 있는 합성 곡 18개로 시험했습니다. 킥, 스네어, 하이햇, 배음이 있는 베이스와 건반, 비화성음이 20% 섞인 멜로디, 잡음 바닥을 넣었고, 기준음을 18 cent 틀어 놓거나 템포를 ±2% 흔든 곡도 있습니다. 비트 적중은 70 ms 이내 기준이고, 오차는 검출 시각 − 정답 시각입니다. 코드는 박 가운데 시점에서 근음, 성질, 베이스가 모두 맞아야 정답으로 쳤습니다.
| 곡 | BPM (정답) | 비트 적중 | 엇박 | 평균 오차 / 평균 절대 오차 | 박자 | 첫 박 | 코드 |
|---|---|---|---|---|---|---|---|
| 팝, 강한 엇박 하이햇, +18 cent | 97.1 (97) | 96/96 | 0 | 0.1 / 1.5 ms | 4/4 | 24/24 | 100% |
| 팝 | 128.1 (128) | 96/96 | 0 | 0.5 / 1.7 ms | 4/4 | 24/24 | 100% |
| 왈츠 | 150.2 (150) | 48/48 | 0 | 0.5 / 1.8 ms | 3/4 | 16/16 | 100% |
| 셋잇단 발라드 | 58.0 (58) | 48/48 | 0 | −1.4 / 2.0 ms | 12/8 | 12/12 | 100% |
| 8분 아르페지오 발라드, 템포 흔들림 | 67.1 (68) | 64/64 | 0 | −1.0 / 1.9 ms | 4/4 | 16/16 | 98.4% |
| 약한 스네어 록 | 118.0 (118) | 96/96 | 0 | 0.0 / 1.6 ms | 4/4 | 24/24 | 100% |
| 댄스(4-on-the-floor, 엇박 오픈햇) | 123.9 (124) | 96/96 | 0 | −0.4 / 1.5 ms | 4/4 | 24/24 | 100% |
| 힙합(16분 하이햇, 싱코페이션 킥) | 87.9 (88) | 64/64 | 0 | 0.7 / 2.0 ms | 4/4 | 16/16 | 100% |
| 펑크 | 176.2 (176) | 96/96 | 0 | 0.4 / 1.7 ms | 4/4 | 24/24 | 100% |
| 피아노 솔로 | 72.0 (72) | 64/64 | 0 | −3.2 / 3.2 ms | 4/4 | 16/16 | 98.4% |
| 피아노 솔로, 3박자 | 84.0 (84) | 48/48 | 0 | −2.6 / 2.7 ms | 3/4 | 16/16 | 100% |
| 발라드, 후반부 15% 느려짐(리타르단도) | 71.6 (72→63) | 80/80 | 0 | −0.8 / 2.0 ms | 4/4 | 20/20 | 88.8% |
| 전조 C → D (A7 경유) | 110.2 (110) | 116/116 | 0 | 0.0 / 1.5 ms | 4/4 | 29/29 | 100% |
| 전조 E♭ → E (B7 경유, 발라드) | 70.0 (70) | 116/116 | 0 | −1.1 / 2.0 ms | 4/4 | 29/29 | 93.1% |
| 전조 G → A♭ (바로) | 100.0 (100) | 112/112 | 0 | 0.0 / 1.4 ms | 4/4 | 28/28 | 100% |
| A 단조 절 → C 장조 후렴(전조 아님) | 96.0 (96) | 64/64 | 0 | −0.2 / 1.5 ms | 4/4 | 16/16 | 100% |
| 드럼만 나오는 인트로 + 못갖춘마디 | 104.0 (104) | 57/57 | 0 | −0.7 / 2.3 ms | 4/4 | 14/14 | 100%, N.C. 9/9 |
| 레게 원드롭 | 151.7 (76) | 48/48 | 47 | −3.8 / 3.8 ms | 4/4 | 두 배 층위 | 100% |
레게는 두 배 층위로 잡혀 나머지 비트가 모두 8분음표 위치에 옵니다. 앞에서 말한 의도적 비해결이며, ÷2 한 번으로 76 BPM이 됩니다. 코드 오답은 Esus4 → E 가 마디 가운데에서 바뀌는 곳에서 E가 한 박 늦게 잡힌 경우와, 발라드형 아르페지오 곡 두 개(리타르단도, E♭ → E)에서 아르페지오 속 7음이 약해 maj7·m7을 3화음으로 읽은 경우입니다(국소 템포나 전조 추적을 끄고 켜도 같은 결과라 박·조성 추적과는 무관).
전조가 없는 합성 곡 14개는 모두 조성 하나로 나왔습니다(가짜 전조 0). 전조 위치의 정답은 새 조의 으뜸화음이 시작하는 마디입니다.
| 곡 | 정답 | 결과 |
|---|---|---|
| C 장조 → D 장조, A7 경유 | 18마디부터 D 장조 | 18마디 1박, 오차 0박 |
| E♭ 장조 → E 장조, B7 경유(발라드) | 18마디부터 E 장조 | 18마디 1박, 오차 0박 |
| G 장조 → A♭ 장조, 경유 없이 바로 | 17마디부터 A♭ 장조 | 17마디 1박, 오차 0박 |
| A 단조 절 → C 장조 후렴 | 전조 없음(조표 같음) | 전조 없음 |
| 못갖춘마디로 시작하는 곡 | 시작 조성 위치 | 0마디 4박 (못갖춘마디 안의 실제 박) |
전조 뒤 표기도 확인했습니다. D 장조 구간은 F♯m, E 장조 구간은 G♯m7·C♯m, A♭ 장조 구간은 D♭·B♭m7으로 적습니다.
| 항목 | 결과 |
|---|---|
| 24개 조성의 번호를 표준 카멜롯 휠 표와 대조 | 24/24 일치 |
| 코드 종류별 변환 | C → 8B, Am → 8A, G7 → 9B7, Dm7 → 7A7, Cmaj7 → 8Bmaj7, Esus4 → 12Bsus4, Bdim → 10Adim, Bm7♭5 → 10A7♭5, Bdim7 → 10Adim7, G/B → 9B |
| 실제 녹음(「세월이 가면」)에서 표기 전환 | 조성 E♭ 장조(5B) → E 장조(12B), 전조 이름표 “12B로”. 차트, 지금·다음 코드, 텍스트 차트, 스크린 리더용 이름이 모두 카멜롯으로 바뀌고, 음이름을 고르면 돌아옴 |
| 분석 결과 | 표기를 바꿔도 박, 첫 박, 코드 판정은 그대로(합성 곡 18개 결과 동일) |
코드가 분명한 합성 음원을 분석해, 인식된 코드의 로마 숫자가 화성학 교재의 표기와 같은지 확인했습니다.
| 조성 | 코드 | 결과 |
|---|---|---|
| A 단조 | Am, Em/G, F, C/E, Dm, G, C, E7 | i, v6, VI, III6, iv, V/III, III, V7 |
| C 장조(부속화음, 차용 화음) | C, E7, Am, D7, G7, C, A7, Dm, G7, C, Fm, C, B♭, C | I, V7/vi, vi, V7/V, V7, I, V7/ii, ii, V7, I, iv, I, ♭VII, I |
| E♭ 장조(부속 ii, 자리바꿈) | E♭, B♭/D, Cm, B♭m7, E♭7, A♭maj7, B♭7/A♭, Gm7, Cm7, Fm7, B♭7, E♭ | I, V6, vi, ii7/IV, V7/IV, IVM7, V⁴₂, iii7, vi7, ii7, V7, I |
실제 녹음(「세월이 가면」)의 코드 105개 가운데 10개가 부속화음(ii7/IV, V7/IV, V7/ii 등), 16개가 자리바꿈으로 적혔고, 전조 직전의 B7은 새 조성 E 장조의 V7로 적혔습니다. 조성이 분명한 합성 곡 18개의 조성 판정은 코드와 음 분포를 섞은 지금 방식으로 18곡 모두 맞았습니다(음 분포만 쓰던 이전 방식은 가단조 진행을 E 단조로 골라 17곡).
상업 녹음 한 곡(기현 「세월이 가면」, 응답하라 1988 OST, 3분 50초)으로 시험했습니다. 정답 박 표시가 없으므로, 템포가 일정한 스튜디오 녹음이라는 점을 이용해 구간마다 비트를 직선에 맞춘 잔차를 추적 오차의 대리 지표로 썼습니다.
| 항목 | 이전 | 지금 |
|---|---|---|
| 박 단계 | 135 BPM(8분음표를 박으로) | 68.1 BPM |
| 36~40초 구간 차트 | A♭maj7 / Gm7 B♭/F │ Fsus4 / B♭ / (반 박마다 바뀜) | A♭maj7 / / B♭/F │ Gm7 / E♭ / |
| 길이가 다른 마디 | 3개(3박, 2박, 3박) + 끝 | 1개 + 끝 |
| 구간별 직선 잔차 RMS | 96~222 ms(고정 벌점 100, 제 템포에서) | 6~23 ms |
| 33~48초 구간 클릭 간격(브라우저) | 0.80~1.02 s | 0.86~0.90 s |
| 전조 | 표시 안 됨, 전조 뒤도 E♭ 기준 표기(A♭m7 등) | 45마디 1박(2:37)부터 E 장조(B♭7 → B7 → E의 E 자리), 이후 G♯m7·C♯7 표기 |
헤드리스 Chromium에서 페이지를 실제로 조작했습니다. AudioBufferSourceNode.prototype.start 를 가로채 예약되는 클릭을 모두 기록하고, 직전의 음악 시작 정보로 곡 안 시각( $o_s + t^\text{click} - t_s$ )을 되살려 정답 비트와 대조했습니다. 재생, 마디 건너뛰기, 정지를 섞어 실행했습니다.
| 파일 | 클릭 | 정박 | 엇박 | 평균 / 최대 오차 | 첫 박 강조 위치 |
|---|---|---|---|---|---|
| 팝 97 BPM | 37 | 37 | 0 | 0.1 / 8.1 ms | 22/22 실제 첫 박 |
| 못갖춘마디 104 BPM | 29 | 29 | 0 | 1.9 / 9.7 ms | 16/16 |
| 발라드 68 BPM (자동 절반 교정) | 22 | 22 | 0 | 1.9 / 8.5 ms | 13/13 |
| 보정 조작 | 결과 |
|---|---|
| ½박 옮기기 | 클릭 4/4가 정답 박의 중간점으로 이동, 한 번 더 누르면 5/5 정박 복귀 |
| ×2, 이어서 ÷2 | 간격 310 ms(박 + 중간점) → 622 ms 정박 복귀 |
| 한 박 늦추기 | 첫 박 강조가 2박째로 이동 |
| 클릭 타이밍 +30 ms | 클릭이 29.2 ms 늦게 예약됨 |
| 박자 3으로 지정 | 3/4, 마디마다 빗금 3개 |
| 메트로놈 끄기(M) | 예약된 클릭 0 |
그 밖에 콘솔 오류 0건, 데스크톱과 휴대폰 폭에서 가로 넘침 0 px을 확인했습니다. 63초 곡은 파일 선택부터 차트 표시까지 약 1.6초(헤드리스 Chromium), 5분 곡 분석은 약 2.7초(Node 22)가 걸렸습니다(리눅스 환경에서 측정).
예약되는 피아노 음도 같은 방법으로 가로챘습니다. 음 버퍼마다 음높이를 추정하고(자기상관으로 대략 찾은 뒤 괴르첼(Goertzel) 스캔으로 기본 부분음 주파수를 정밀 측정), 같은 시각에 시작하는 음들을 한 번의 타건으로 묶어 정답 코드와 대조했습니다. 앱 코드에는 시험용 표시를 넣지 않았습니다.
| 항목 | 결과 |
|---|---|
| 코드가 바뀔 때 | 타건 18회 모두 구성음 일치, 베이스 18/18 일치 |
| 타건 시각 | 실제 코드 변화 시각과의 오차 평균 1.4 ms, 최대 8.1 ms |
| 코드 중간에서 재생 시작 | 재시작 타건 10회 모두 그 순간의 정답 코드 |
| 마디마다, 박마다 | 3/3, 5/5 정답, 박 시각 오차 평균 3.0 ms, 0.6 ms |
| 피아노 기준음 | 곡 튜닝 +18 cent에 대해 11개 음의 기본 부분음 +17.7~+18.3 cent |
| P 키로 끄기 | 이후 피아노 음 예약 0, 메트로놈은 계속 |
| 메트로놈 영향 | 리미터를 넣은 뒤에도 클릭 37/37, 29/29, 22/22 정박, 첫 박 강조 모두 정확 |
| 메인 스레드 | 재생 중 50 ms 넘는 작업 0건 |
| 음량과 울림 | 실제 앱의 합성 함수와 소리 경로를 OfflineAudioContext로 렌더링해 이전 버전과 같은 슬라이더에서 비교. 첫 1초 RMS +5.1~+6.5 dB(약 1.8~2.1배), 1–2초 꼬리 +2.7~+3.0 dB, 10 dB 감쇠 시간 1.3~1.8배 (C, G7, Am). 피크는 기본 볼륨 −7 dBFS, 최대 볼륨 −2.3~−3.0 dBFS |
회귀 18곡에 없던 곡 종류 17곡을 더해 1.4.2와 비교했습니다. 대중음악형 10곡(템포 급변, 루바토 피아노, 코드 밖 베이스, 테이프 음높이 변화, 케이팝·EDM, 팝록 파워 코드, R&B 슬래시 발라드, 드럼이 큰 힙합, 드럼이 아주 큰 록, 어쿠스틱 스트로크 팝)과 특수 장르 7곡(5/4, 7/4, 스윙 재즈, 비밥, 보사노바, 메탈, 앰비언트)입니다.
검증 시 기준은 대중음악을 우선하는 것입니다. 대중음악 곡(회귀 18곡, 대중음악형 곡, 실제 녹음)의 결과가 나빠지는 수정은 특수 장르가 좋아져도 넣지 않고, 대중음악 곡의 결과가 좋아지는 수정은 특수 장르가 조금 나빠져도 넣습니다.
| 묶음 | 항목 | 1.4.2 | 1.5.0 |
|---|---|---|---|
| 회귀 18곡 | 비트, 첫 박, 코드, 조성 | 100%, 100%, 98.6%, 18곡 | 같음 |
| 대중음악형 10곡 | 비트 | 95.7% | 99.6% |
| 엇박 클릭 | 16 | 1 | |
| 첫 박 | 95.7% | 99.5% | |
| 코드(정확) | 88.1% | 97.0% | |
| 특수 장르 7곡 | 비트 | 80.0% | 93.8% |
| 첫 박 | 74.4% | 93.2% | |
| 코드(정확) | 52.7% | 56.0% | |
| 실제 녹음 | 1.4.2와 일치 | 코드 100%, 첫 박 66/66, 비트 257/257 |
곡별로는 템포 급변 곡의 비트가 64→93/96(엇박 16→1), 5/4와 7/4의 박자가 정확히 판정되고(첫 박 16/16, 12/12), 비밥의 비트가 24→96/96, 코드 밖 베이스 곡이 50→100%, R&B 슬래시 발라드가 82→91%, 팝록 파워 코드 곡이 50→88%가 되었습니다.
비교했지만 넣지 않은 방법
| 방법 | 대중음악 | 특수 장르 | 판정 |
|---|---|---|---|
| 7화음의 9·13 확장음 변형 | 실제 녹음 코드의 18%가 바뀜(A♭→A♭maj7 등) | 스윙 재즈 계열 일치 28→32% | 넣지 않음 |
| 화성·타악기 분리(HPSS) | 변화 없음, 실제 녹음 코드 3% 바뀜, 분석 시간 2배 | 코드 정확 56→61% | 넣지 않음 |
| NNLS 음 분해 크로마 | 회귀 코드 98.6→97.4%, 대중음악형 97.0→95.7% | 코드 정확 56→63% | 넣지 않음 |
| 바 포인터 모델 | 회귀 엇박 클릭 47→304, 첫 박 100→56.7% | 첫 박 93→44% | 넣지 않음 |
바 포인터 모델은 원래 학습된 신경망이 만든 박·첫 박 활성과 함께 쓰는 방법이라, 손으로 만든 온셋·강세 곡선으로는 박의 층위와 박자를 가르지 못했습니다. 네 방법은 모두 엔진 안에 꺼진 설정으로 남아 있어 같은 비교를 다시 해 볼 수 있습니다.
| 곡 | 정답 | 결과 |
|---|---|---|
| 팝, 4/4 → 3/4 → 4/4(각 8마디) | 9마디부터 3/4, 17마디부터 4/4 | 같음, 첫 박 24/24 |
| 왈츠 절 → 4/4 후렴 | 9마디부터 4/4 | 같음, 첫 박 20/20 |
| 템포 급변 96 → 132 | 13마디 1박부터 132 | 같음 |
| 후반부 15% 느려지는 발라드 | 후반부가 서서히 72 → 63 | 11마디 2박부터 20마디 4박까지 서서히 72 → 63(육각형 틀), 20마디 4박에 흰 알약 63 |
| 박자가 일정한 35곡과 실제 녹음 | 박자 변화 없음 | 가짜 박자 변화 0 |
| 나머지 36곡과 실제 녹음 | 서서히 바뀌는 구간 없음 | 육각형 틀 0 |
박자 변화 모델을 켠 뒤에도 1.5.0과 비교한 37곡의 비트, 첫 박, 코드, 조성은 모두 같았고, 실제 녹음은 1.4.2와 코드 100%, 첫 박 66/66, 비트 257/257이 같았습니다. 템포가 무작위로 ±12%까지 흔들리는 루바토 피아노에는 12% 넘게 갑자기 빨라진 곳 한 군데에 표시가 붙었습니다.
1.10.0에서 박 단계, 첫 박, 코드 이름 규칙을 고치면서 사람이 단 정답이 있는 실제 녹음으로 1.9.4와 비교했습니다. GTZAN은 10개 장르의 30초 발췌 998곡에 박과 첫 박 정답이 있고, GuitarSet은 독주 기타 반주 180곡에 코드, 박, 첫 박 정답이 있습니다. 비트와 첫 박은 ±70 ms 안의 일대일 짝으로 F값을 쟀고, 코드는 정답 박 가운데 시점에서 근음과 장·단 계열(maj·7·maj7은 장, m·m7은 단)이 맞는지 셌습니다(GuitarSet은 리드시트 정답). 대중음악 무리는 GTZAN의 블루스, 컨트리, 디스코, 메탈, 팝, 록과 GuitarSet의 록, 싱어송라이터, 펑크입니다.
| 자료 | 항목 | 1.9.4 | 1.10.0 |
|---|---|---|---|
| GTZAN 대중음악 600곡 | 비트 F / 첫 박 F | 0.829 / 0.557 | 0.837 / 0.608 |
| 제 템포·정박으로 잡은 곡 / 절반 템포로 잡은 곡 | 452 / 54 | 465 / 39 | |
| GTZAN 998곡 전체 | 비트 F / 첫 박 F | 0.786 / 0.511 | 0.794 / 0.556 |
| 3·5·7박으로 적은 곡 | 221 | 104 | |
| GuitarSet 대중음악 108곡 | 비트 F / 첫 박 F | 0.656 / 0.550 | 0.663 / 0.568 |
| 코드 근음 / 장·단 | 81.7% / 68.5% | 81.9% / 69.0% | |
| GuitarSet 180곡 전체 | 코드 근음 / 장·단 | 77.0% / 66.8% | 77.8% / 67.9% |
GTZAN 장르별 첫 박 F값은 블루스 0.328 → 0.361, 컨트리 0.778 → 0.794, 디스코 0.676 → 0.708, 메탈 0.363 → 0.476, 팝 0.676 → 0.717, 록 0.519 → 0.593, 힙합 0.482 → 0.541, 레게 0.634 → 0.679, 재즈 0.349 → 0.360, 클래식 0.293 → 0.315로 모두 올랐습니다. 곡별로는 대중음악 600곡 가운데 첫 박 F가 0.05 넘게 오른 곡이 61곡, 내린 곡이 34곡입니다. 내린 곡 가운데 22곡은 4/4 곡을 7/4·5/4·3/4로 잘못 적던 것이 4/4로 바로잡히면서 우연히 맞던 첫 박 몇 개를 잃은 경우이고, 박자가 같은 채 첫 박 위치가 나빠진 곡은 8곡입니다. GuitarSet에서는 펑크 두 곡(114 BPM)이 절반 템포에서 제 템포로 바로잡히면서 박이 촘촘해져 코드 근음이 6~9%p 내렸습니다.
대중가요 녹음 세 곡으로도 확인했습니다. 「고민중독」은 공개된 코드 차트를 음원의 드럼과 베이스에 맞춰 마디 단위로 정렬한 정답으로 쟀고(확신할 수 있는 62% 구간만), 「크리스마스의 기억」은 알려진 템포(170 BPM)와 박자만 봤습니다.
| 곡 | 항목 | 1.9.4 | 1.10.0 |
|---|---|---|---|
| QWER 「고민중독」(정답 178 BPM, 4/4 129마디) | 템포 | 89.0(스네어 위 절반 템포) | 177.9 |
| 비트 F / 첫 박 F / 불규칙 마디 | 0.64 / 0 / 9 | 0.96 / 0.95 / 3 | |
| 코드 근음 / 장·단(박마다) | 61.6% / 50.5% | 85.5% / 83.5% | |
| 마디 단위 코드 | 66.7% | 91.3% | |
| 7·sus 등 꾸밈이 붙은 이름의 비율 | 91% | 67% | |
| 조성 | E 단조 → F 단조 | G 장조 → A♭ 장조(104마디부터, 정답과 같음) | |
| Warak 「크리스마스의 기억」(170 BPM) | 템포 / 불규칙 마디 | 85.0 / 3 | 170.1 / 2(곡 첫 마디와 끝 마디) |
| 기현 「세월이 가면」 | 1.4.2와 일치(코드, 첫 박, 비트) | 97.7%, 66/66, 257/257 | 같음 |
합성 곡 37곡(회귀 18곡, 대중음악형 10곡, 특수 장르 9곡)과 처음 보는 합성 곡 37곡의 비트, 첫 박, 코드, 조성은 1.9.4와 곡별로 같습니다.
장르와 언어가 다른 대중가요 다섯 곡으로 템포, 박자, 마디 위치, 조성, 코드를 확인했습니다. 마디 위치는 독립된 분석기(madmom: 학습된 박·첫 박 추적과 장·단 코드 인식)와 비교했고, 코드는 공개된 코드 차트를 음원에 맞춰 박 단위로 정렬하고 차트와 음원이 다른 곳(커버 편곡 등)은 베이스와 화음 대역을 재서 정한 정답으로 0.1초마다 쟀습니다(근음, 장·단 계열). 정답과 madmom 결과는 저작권 때문에 배포하지 않고 점수만 적습니다. 「여래아」는 공개된 코드 차트가 없어 템포·박자·마디·조성만 봤습니다.
| 곡 | 템포(공개 값) | 박자 | 첫 박 일치(madmom) | 조성 | 코드 근음 / 장·단 1.10.2 → 1.10.3 | 같은 채점의 madmom |
|---|---|---|---|---|---|---|
| 여자친구 「시간을 달려서」 | 112.0 (112) | 4/4 | 0.96 → 0.99 | F 단조 | 89.0% / 86.2% → 89.5% / 86.8% | 83.7% / 84.0% |
| 「もう恋なんてしない」 여성 보컬 커버(원곡보다 7반음 높고 약 5% 빠름) | 109.9 | 4/4 | 0.99 | B 장조 | 90.1% / 92.9% → 90.4% / 92.9% | 84.1% / 88.1% |
| あいみょん 「マリーゴールド」 | 106.0 (106) | 4/4 | 0.96 | D 장조 | 89.4% / 88.9% → 90.4% / 89.9% | 92.9% / 92.9% |
| YOASOBI 「夜に駆ける」 | 130.0 (130) | 4/4 | 1.00 | C 단조 → B 단조 → D 단조 | 88.6% / 80.6% → 88.6% / 80.5% | 83.4% / 82.7% |
| 시드 사운드 「여래아」 | 90.0 | 4/4 | 1.00 | C 단조 → D 단조(1.10.2는 C 단조 → F 장조) | 차트 없음 | - |
다섯 곡 모두 템포 층위와 박자가 맞고, 마디 위치는 madmom·코드 차트와 같습니다. 「시간을 달려서」의 첫 두 마디는 1.10.3에서, 「マリーゴールド」의 도입부 마디선은 1.10.4에서 바로잡았습니다. 「マリーゴールド」 도입부는 madmom도 다르게(1.78·4.78초) 잡으므로 첫 박 일치는 그대로 0.96이고, 마디선은 도입부 리프가 되풀이되는 주기와 시작점으로 확인했습니다. 「夜に駆ける」의 반음 내림·올림 전조는 실제로 있으며, 반음 내린 조는 1.10.3까지 두 마디 일찍(102마디) 표시되다가 1.10.4부터 B 단조 후렴이 시작하는 104마디에 표시됩니다. 남은 코드 오류는 대부분 노래 선율이나 지속음이 화음 대역에 섞여 공통음이 많은 이웃 코드(A → Bm7/A, G7 → Gm7, C → Cm7 등)로 읽히는 것, 반 마디·한 박짜리 짧은 코드를 놓치는 것, sus4가 3화음으로 풀리는 것을 한 코드로 합치는 것입니다. 높은 음역의 비중을 낮추는 방법은 이 다섯 곡에서는 근음을 0.6~1.1%p 올렸지만 합성 R&B 발라드, GuitarSet, 「고민중독」을 나쁘게 해 넣지 않았습니다.
1.10.4(도입부 격자, 전조 경계 다듬기)는 다섯 곡의 코드·다른 마디·다른 조 경계를 바꾸지 않았고, 합성 곡 37곡, 회귀 18곡, 조성·로마 숫자 시험, GuitarSet, 「고민중독」·「세월이 가면」의 코드·첫 박·비트, Warak, GTZAN 점수도 모두 그대로입니다. 조 경계는 「고민중독」에서 정답 쪽으로 한 마디 옮겨졌고, 조성 정답이 없는 GTZAN 30초 발췌 1000곡 중 9곳(클래식 3, 메탈 4, 재즈·록 1씩)과 GuitarSet 1곡에서 전조 표시가 1~2마디 뒤로 옮겨졌습니다(짧은 발췌라 대부분 원래 없는 전조로 보임).
1.10.3의 다른 시험 결과: 합성 곡 37곡은 곡별로 같고, 「고민중독」은 근음 85.5% → 85.9%, 장·단 83.5% → 83.8%, GuitarSet 대중음악은 근음 그대로(81.9%)·첫 박 F 0.568 → 0.574, GTZAN 대중 6장르는 비트·첫 박 F 그대로에 불규칙 마디 711 → 695개, 「세월이 가면」은 1.4.2와 97.7%, 66/66, 257/257 그대로입니다.
1.10.3의 다섯 곡에 장르와 빠르기가 다른 다섯 곡(김종국 「이 사람이다」, 015B 「이젠 안녕」, 박명수 「바다의 왕자」 라이브 커버, 디셈버 「별이 될게」·피노키오 「사랑과 우정 사이」 여성 보컬 커버)과 「세월이 가면」(기현, 응답하라 1988 OST)을 더해, 코드 정답이 있는 실제 녹음 10곡으로 검증 키트를 만들었습니다. 곡마다 공개 코드 차트 둘 이상을 음원의 박에 맞춰 정렬하고, 차트와 음원이 다른 곳은 베이스와 화음 대역을 재서 정한 뒤, 따로 의심하며 다시 확인해(곡마다 1~9곳 고침) 박·첫 박·조성 정답과 함께 만들었습니다. 정답은 저작권 때문에 배포하지 않습니다. 「여래아」는 공개 차트가 없어 빠졌고, 「고민중독」은 따로 채점합니다.
규칙과 값은 앞의 네 곡과 「고민중독」, GuitarSet, 합성 곡으로만 골랐고, 새 다섯 곡과 「세월이 가면」은 값을 고를 때 보지 않고 마지막에만 쟀습니다.
| 곡 | 코드 근음 / 장·단 1.10.5 → 1.11.0 | 같은 채점의 madmom |
|---|---|---|
| 여자친구 「시간을 달려서」 | 89.3% / 86.5% → 91.8% / 90.0% | 83.7% / 84.0% |
| 「もう恋なんてしない」 커버 | 90.4% / 92.6% → 92.8% / 94.1% | 84.1% / 88.1% |
| あいみょん 「マリーゴールド」 | 90.4% / 89.8% → 97.0% / 94.9% | 92.9% / 92.9% |
| YOASOBI 「夜に駆ける」 | 88.6% / 80.5% → 90.7% / 85.0% | 83.4% / 82.7% |
| 김종국 「이 사람이다」 | 87.6% / 83.5% → 90.0% / 85.6% | 83.3% / 80.9% |
| 015B 「이젠 안녕」 | 85.8% / 83.2% → 91.0% / 88.8% | 83.2% / 82.7% |
| 박명수 「바다의 왕자」 라이브 커버 | 94.0% / 93.6% → 99.3% / 99.3% | 90.3% / 90.3% |
| 디셈버 「별이 될게」 커버 | 90.4% / 89.8% → 90.9% / 88.5% | 88.6% / 88.5% |
| 피노키오 「사랑과 우정 사이」 커버 | 97.7% / 96.2% → 98.0% / 95.8% | 91.1% / 91.9% |
| 기현 「세월이 가면」 | 88.4% / 84.8% → 94.7% / 91.2% | 90.8% / 89.8% |
| 앞의 4곡(값을 고른 곡) | 89.7% / 87.2% → 93.3% / 91.0% | 86.5% / 87.3% |
| 뒤의 6곡(값을 고를 때 안 본 곡) | 90.8% / 88.6% → 94.0% / 91.5% | 88.0% / 87.5% |
| 10곡 전체 | 90.4% / 88.0% → 93.7% / 91.3% | 87.4% / 87.4% |
채점은 0.1초 표본마다 엔진 코드의 근음과 장·단 계열이 정답 라벨 중 하나와 같은지 보며, 코드가 없는 곳(첫 박 앞, 끝)도 틀림으로 셉니다. 1.10.3 절의 표는 시각에 맞는 코드를 찾는 방식과 이런 표본을 장·단 분모에서 빼는 점이 다른 옛 채점이라 값이 0.1~0.7%p 다릅니다.
10곡 모두 근음이 올랐고, 근음이 틀린 표본은 9.6%에서 6.3%로 줄었습니다(공통음이 많은 이웃 코드로 읽은 것 5.0% → 3.6%). 장·단은 「별이 될게」와 「사랑과 우정 사이」에서 조금 내려갔습니다. 근음과 성질(장·단·7·maj7·m7·sus 등)까지 맞는 비율은 64.3%에서 66.0%로 올랐습니다(정답 차트가 3화음만 적은 앞의 4곡은 이 비율이 원래 낮음). 박·첫 박·조성은 10곡 모두 그대로입니다. 「이젠 안녕」(정답 71 BPM)과 「사랑과 우정 사이」(64 BPM)는 8분음표를 박으로 세는 두 배 템포로 잡히는데, 박 단계 검증의 조건을 넓히면 GTZAN에서 제 템포 곡이 더 많이 절반으로 내려가 고치지 않았습니다(4-8).
「세월이 가면」 시험 기준을 정답으로 바꿈(1.11.0). 이전까지 이 곡은 1.4.2 결과와 같은지(코드 일치 97.7%, 첫 박 66/66, 비트 257/257)로 퇴행을 확인했는데, 1.4.2 결과 자체가 정답과 견주면 근음이 약 88%였습니다. 1.11.0부터는 정답과 비교해 기록값(근음 94.7%, 장·단 91.2%, 성질 78.1%, 비트 F 0.948, 첫 박 F 0.937)보다 낮아지면 퇴행으로 보고, 1.4.2와의 일치는 참고로만 둡니다(1.11.0에서 코드 85.9%, 첫 박·비트는 그대로 66/66, 257/257).
1.11.0의 다른 시험 결과: 합성 곡 37곡은 근음 96.52% → 96.81%, 장·단 계열 95.60% → 95.78%(비밥·스윙 재즈·R&B 발라드 세 곡이 나아지고 나머지는 같음, 비트·첫 박 그대로), 「고민중독」 근음 85.9% → 93.3%, 장·단 83.8% → 88.2%, GuitarSet 대중음악 근음 81.87% → 82.47%, 장·단 68.97% → 69.72%, GuitarSet 전체 근음 77.87% → 78.35%, 회귀 18곡·조성·로마 숫자 시험과 GTZAN·Warak의 박·첫 박·박자는 그대로입니다. GTZAN은 1000곡 중 23곡의 조성이 바뀌었는데, 공개된 GTZAN 조성 정답(Lerch)과 견주면 7곡이 맞게, 5곡이 틀리게 바뀌어(나머지는 정답이 없거나 둘 다 틀림) 대중 6장르 조성 정확도가 55.3%에서 56.0%로 올랐습니다(나란한 조·5도 관계에 부분 점수를 주는 MIREX 점수 64.4% → 65.0%). 스테레오 파일은 분석 시간이 곡당 약 40% 늘어납니다(키트 10곡 평균 2.9초 → 4.0초, 같은 컴퓨터).
「해질녘 예스터데이」 둘째 마디는 첫 박에 A♯m7, 둘째 박부터 D♯m7을 칩니다. 첫 박만 보면 A♯m7이 D♯m7보다 3.79 앞서지만, 둘째 박에서 코드를 바꾸는 벌점(4.0)보다 작아 1.11.7에서는 마디 전체가 D♯m7로 나왔습니다.
처음에는 “첫 박에서 다른 코드가 3.5 넘게 앞서고 둘째 박에서는 지금 코드가 나으면 첫 박을 그 코드로”라는 점수 조건만 두었습니다. 그러자 시험곡 1,268곡 가운데 102곡이 바뀌었고, 코드 정답이 있는 GuitarSet에서 점수가 움직인 6곡이 모두 나빠졌습니다. 정답을 보면 그 자리는 모두 새 코드가 첫 박부터 시작하는 곳이었고, 첫 박에서 보인 다른 코드는 앞 코드의 잔향이 새 코드와 섞인 소리였습니다. 점수 차로는 이 곡과 그 곡들을 가를 수 없었습니다(GuitarSet 00_Jazz3-150-C는 3.94 차이로 이 곡보다 컸음). 첫 박을 반으로 나눠 뒤쪽 반 박에서도 앞서는지 보는 조건은 오히려 이 곡의 A♯m7이 뒤쪽에서 약해(0.88) 쓰지 않았습니다.
두 코드를 정말 따로 쳤는지는 소리의 시작(어택)으로 갈렸습니다. 이 곡은 A♯m7에만 있는 음이 첫 박에서 2.25배, D♯m7에만 있는 음이 둘째 박에서 2.03배로 새로 울리고 두 코드의 베이스가 다릅니다. GuitarSet에서 틀렸던 6곳은 첫 박 어택이 약하거나(1.2배 미만 3곳), 둘째 박에 원래 코드를 다시 치지 않았거나(2곳), 두 코드의 베이스가 같았습니다(1곳). 어택 문턱은 이어짐(회색) 판정과 같은 1.2배를 그대로 썼습니다.
| 조건 | 바뀌는 곡(1,268곡 중) | GuitarSet 코드 채점 |
|---|---|---|
| 점수 차 3.5 이상 | 102 | 6곡 나빠짐 |
| + 어택·베이스 조건, 점수 차 3.0 이상 | 51 | 2곡 나빠짐, 1곡 좋아짐 |
| + 어택·베이스 조건, 점수 차 3.25 이상 | 34 | 바뀐 곡 없음 |
| + 어택·베이스 조건, 점수 차 3.5 이상(채택) | 23 | 바뀐 곡 없음 |
3.25도 정답이 있는 곡에서는 나빠진 곳이 없었지만, 나빠지는 곡이 나오는 3.0과 가까워 여유를 두고 3.5로 정했습니다. 최종 조건으로 바뀌는 곡은 23곡(곡마다 한두 마디)입니다. 「해질녘 예스터데이」는 둘째 마디가 A♯m7 D♯m7로 돌아왔고, 끝부분 93마디 첫 박에 D♭이 더해졌습니다(A♭sus4 Fm7/C B♭m7 → D♭ A♭sus4 Fm7/C B♭m7). 실제 녹음 키트의 「고민중독」은 2분 21초 무렵 한 마디의 첫 박에 B7/F♯이 더해졌는데, 정답 차트에서 코드가 적히지 않은 칸이라 점수는 그대로입니다(같은 곡의 m4a 파일도 같음). 나머지 20곡은 코드 정답이 없는 GTZAN 곡입니다. 회귀 18곡·조성·로마 숫자 시험, 합성 곡 37곡 채점, 「세월이 가면」 정답 시험, GuitarSet 180곡 코드·박 채점은 1.11.7과 같고, 바뀐 곡도 박·첫 박·박자·조성은 그대로입니다.
사용자가 알려 준 「해질녘 예스터데이」는 드럼이 들어오는 10.88초까지 피아노 도입부인데, 박이 0.40~0.53초 간격으로 들쭉날쭉하게 잡혀 첫 마디부터 코드가 어긋났습니다. 본 구간 박 주기(0.455초, 132 BPM)를 거꾸로 이으면 첫 소리(3.14초)에 정확히 닿고, 도입부 피아노 온셋이 거의 모두 그 격자의 16분음표 자리에 놓여(온셋 봉우리 58개 중 51개가 ±25 ms 안), 처음부터 132 BPM 고정 템포로 만든 곡임을 확인했습니다. 지금까지는 16분음표 앞당김(5.31·9.40초 등)을 박으로 따라가 박이 최대 0.12초 일렀습니다. 1.11.7에서는 도입부 박이 0.455초 간격으로 놓이고, 셋째 마디가 G♯m G♯m7에서 G♯m7 C♯7로 바뀌었습니다(5도씩 내려가는 진행). 다만 둘째 마디는 A♯m7 D♯m7에서 D♯m7로 바뀌었는데, 바른 격자에서는 A♯m7이 첫 박(과 16분음표 하나)만 울려 한 박짜리 짧은 코드가 이웃 코드에 합쳐진 것입니다. 이것은 1.11.8에서 고쳤습니다(아래 1.11.8 검증). 첫 마디는 B G♯7/F♯에서 G♯m7/B로 바뀌었고, 도입부 밖에서는 되풀이되는 구간끼리 코드 점수를 모을 때 바뀐 도입부 박이 짝으로 쓰여 44마디 넷째 박 하나가 Em7에서 Cmaj7로 바뀌었습니다(그 박의 음으로는 Em7 쪽이 더 그럴듯함). 나머지 마디는 그대로입니다.
시험곡 1,268곡에서 조건을 하나씩 더하며 바뀌는 곡을 셌습니다. 고른 템포·국소 벗어남·16분음표 맞춤 조건만으로는 4곡이 바뀌었고(GTZAN 2곡은 박·첫 박 점수 그대로), GuitarSet 05_Funk2-119-G는 박·첫 박 점수는 같지만 길이가 다른 마디가 하나 생기고 코드 근음 0.254 → 0.222, 장·단 0.316 → 0.263으로 내려가, 고정 템포 근거 조건(온셋 봉우리 30개 이상, 16분음표 맞춤 0.85 이상)을 더해 뺐습니다. 이 곡은 처음부터 끝까지 기타 혼자 연주해 도입부 온셋이 19개뿐이고 맞춤도 0.84였습니다. 최종 조건으로 바뀌는 곡은 「해질녘 예스터데이」 하나이고, 나머지 1,267곡과 회귀 18곡·조성·로마 숫자 시험, 합성 곡 37곡 채점, 실제 녹음 키트, 「세월이 가면」 정답 시험, GTZAN·GuitarSet 채점은 1.11.6과 같습니다. 같은 모양의 도입부(드럼 없이 시작하는 고정 템포 곡에서 16분음표 앞당김)를 가진 곡이 시험곡에 이 곡 말고는 없어서, 이 규칙이 다른 곡을 얼마나 고치는지는 아직 재지 못했습니다.
1.11.5와 같은 방법으로, 박이 셋으로 쪼개져 보이는 125곡 가운데 4:3 확인으로 바뀌지 않는 119곡마다 3/2배 빠른 템포로 다시 잡은 결과를 모두 만들어 두고 조건을 하나씩 더했습니다.
| 조건 | 바뀌는 곡 | 좋아짐 | 나빠짐(박 또는 첫 박) |
|---|---|---|---|
| 조건 없이 모두 바꿈 | 119 | 34 | 84 |
| 새 박이 셋잇단이 아니고 60~240 BPM | 107 | 32 | 75 |
| + 화음 바뀜 묶음 $G \ge 0.05$ | 48 | 26 | 22 |
| + 선호 분포 없는 점수 비 0.93 이상 | 12 | 9 | 3 |
| + 새 비트 위치 강세 비 1.05 이상 | 9 | 8 | 1 (기타 반주 1곡, 첫 박) |
| + 새 박이 8분음표 둘로 뚜렷함(1.4배) | 8 | 8 | 0 (박·첫 박 기준) |
| + 새 템포 90 BPM 이상(채택) | 7 | 7 | 0 |
8곡 단계에서 GuitarSet 코드 채점을 돌려 보니 02_BN2-166-Ab(12/8, 56 BPM → 4/4, 83 BPM, 정답 166의 절반)는 박·첫 박이 좋아졌지만 코드 장·단 정확도가 0.44에서 0.22로 내려가, 새 템포 하한을 60에서 90 BPM으로 올려 뺐습니다. 바뀐 곡은 다음 7곡입니다.
| 곡 | 바뀐 것 | 정답과 비교 |
|---|---|---|
| GTZAN metal.00017 | 12/8, 144 BPM → 4/4, 216 BPM(정답 217) | 박 F 0.38 → 0.76, 첫 박 F 0.04 → 0.70 |
| GTZAN metal.00034, metal.00094(같은 음원이 두 번 든 곡) | 12/8, 107 BPM → 4/4, 160 BPM(정답 159) | 박 F 0.38 → 0.88, 0.37 → 1.00 / 첫 박 F 0.12 → 0.35, 0 → 0 |
| GTZAN rock.00064 | 12/8, 102 BPM → 5/4, 152 BPM(정답 152, 정답도 5박 마디) | 박 F 0.39 → 1.00, 첫 박 F 0.21 → 1.00 |
| GuitarSet 01_Rock1-130-A | 12/8, 87 BPM → 4/4, 130 BPM | 박 F 0.40 → 0.99, 첫 박 F 0.29 → 0.96, 코드 근음 0.83 → 0.96, 장·단 0.81 → 0.96 |
| GuitarSet 02_Rock3-148-C | 6/8, 98 BPM → 4/4, 150 BPM | 박 F 0.40 → 0.60, 첫 박 F 0.28 → 0.63, 코드 근음 0.87 → 0.97, 장·단 0.76 → 0.87 |
| GTZAN jazz.00086 | 12/8, 137 BPM → 5/4, 206 BPM(정답 104의 두 배) | 박 F 0.28 → 0.59, 첫 박 F 0.12 → 0.17 |
앞의 6곡은 정답 템포가 되었고, jazz.00086(실제로는 4:3 오류가 있던 곡)은 정답의 두 배가 되어 보정의 ÷2 버튼으로 고칠 수 있게 되었습니다(전에는 12/8이라 버튼으로 고칠 수 없었음). 같은 오류(12/8·6/8로 적히고 템포가 정답의 2/3배)가 있는 시험곡은 GTZAN 10곡, GuitarSet 18곡으로 28곡이고, 그중 6곡이 정답 템포가 되었습니다. 남은 곡은 조건 가운데 하나 이상을 넘지 못했습니다(주로 점수 비 0.93). 이 문턱을 낮추면 템포는 맞아도 비트가 엇박에 붙는 기타 반주 곡(00_BN1-129 등)이 함께 바뀌어 나빠지므로 넓히지 않았습니다. 1,268곡 전체를 1.11.5와 따로 분석해 비교했을 때(하한 60 BPM) 위 7곡과 02_BN2-166 말고는 모두 같았고, 하한을 90으로 올린 뒤 박이 셋으로 쪼개져 보이는 125곡을 다시 돌려 02_BN2-166만 1.11.5로 돌아간 것을 확인했습니다. GuitarSet 180곡 코드 채점은 이 2곡만 좋아지고 나머지는 같으며, 회귀 18곡·조성·로마 숫자 시험, 합성 곡 37곡 채점, 실제 녹음 키트, 「세월이 가면」 정답 시험, GTZAN 조성은 1.11.5와 같습니다.
1.11.4와 같은 시험곡 1,268곡에서, 박이 셋으로 쪼개져 보이는(12/8·6/8로 적히는) 곡 125곡마다 4/3배 느린 템포로 다시 잡은 결과를 모두 만들어 두고, 조건을 하나씩 더하며 바뀌는 곡을 셌습니다. 정답이 있는 곡은 박 F나 첫 박 F 가운데 하나라도 내려가면 나빠진 것으로 셌습니다.
| 조건 | 바뀌는 곡 | 좋아짐 | 나빠짐(박 또는 첫 박) |
|---|---|---|---|
| 조건 없이 모두 바꿈 | 124 | 26 | 96 |
| 새 박이 셋잇단이 아니고 50~140 BPM | 97 | 25 | 70 |
| + 선호 분포 없는 점수 비 0.93 이상 | 14 | 5 + 정답 없는 1곡 | 8 (기타 반주 7곡, 아주 빠른 곡 1곡) |
| + 새 박이 8분음표 둘로 뚜렷함(1.4배) | 9 | 5 + 정답 없는 1곡 | 3 (기타 반주 2곡, 아주 빠른 곡 1곡) |
| + 처음 추정에서도 거의 비김(0.85) | 6 | 5 + 정답 없는 1곡 | 0 |
| + 화음 바뀜 묶음 $G \ge 0$ (채택) | 6 | 5 + 정답 없는 1곡 | 0 |
바뀐 곡은 다음 6곡입니다.
| 곡 | 바뀐 것 | 정답과 비교 |
|---|---|---|
| 「아주 먼 옛날」(사용자 제보) | 12/8, 92 BPM → 4/4, 69 BPM, 길이가 다른 마디 4개 → 0(끝 마디 빼고) | 정답 파일 없음(코드가 마디 첫 박·셋째 박에 놓이고 C–Em–Dm7–G7처럼 고르게 진행) |
| GTZAN pop.00004 | 6/8, 107 BPM → 4/4, 80 BPM | 박 F 0.30 → 1.00, 첫 박 F 0.17 → 1.00 |
| GTZAN pop.00062 | 6/8, 107 BPM → 2/4, 81 BPM | 박 F 0.30 → 1.00, 첫 박 F 0.11 → 0.67 |
| GTZAN hiphop.00035 | 12/8, 112 BPM → 4/4, 84 BPM | 박 F 0.28 → 1.00, 첫 박 F 0.15 → 1.00 |
| GTZAN rock.00049 | 12/8, 111 BPM → 4/4, 84 BPM | 박 F 0.27 → 0.98, 첫 박 F 0 → 0(같음) |
| GuitarSet 00_Rock2-85-F | 12/8, 113 BPM → 4/4, 85 BPM | 박 F 0.30 → 0.99, 첫 박 F 0.21 → 0.97 |
pop.00004와 pop.00062는 다시 잡은 비트가 킥을 따라 엇박에 붙어(박 F 0) 반 박 옮김 규칙이 있어야 좋아졌습니다. 같은 오류(12/8·6/8로 적히고 템포가 정답의 4/3배)가 있는 곡은 GTZAN 11곡, GuitarSet 7곡과 「아주 먼 옛날」로 19곡인데, 이 중 6곡이 고쳐졌습니다. 나머지 13곡은 위 조건 가운데 하나 이상을 넘지 못했고, 조건을 더 넓히면 정답 템포가 3/2배 빠른 기타 반주 곡이나 템포가 아주 빠른 곡이 반 템포·엇박으로 바뀌어 넓히지 않았습니다. 박이 셋으로 쪼개져 보이지 않는 곡은 이 확인을 하지 않으므로 결과가 1.11.4와 같습니다. 1,268곡 전체를 1.11.4 엔진과 이 엔진으로 따로 분석해 비교한 결과도 위 6곡 말고는 모두 같고, 회귀 18곡·조성·로마 숫자 시험, 합성 곡 37곡 채점, 실제 녹음 키트, 「세월이 가면」 정답 시험은 1.11.4와 같습니다.
1.11.4의 세 규칙(박자 변화 확인, 길이가 다른 마디 다시 보기, m7과 6화음이 겹치는 조 이름)은 시험곡 1,268곡(합성 곡 37곡과 처음 보는 합성 곡 37곡, 실제 녹음 15곡(정답이 있는 대중가요 11곡 포함), GTZAN 999곡, GuitarSet 180곡)에서 한 곡도 나빠지지 않게 조건을 정했습니다. 곡마다 한 번 분석한 결과에 규칙을 켜고 끈 두 설정을 모두 적용해 마디·코드·조성·박자·템포를 비교했습니다. 규칙을 끄면 모든 곡이 1.11.3과 같고, 켜면 결과가 바뀐 곡은 다음 7곡뿐입니다.
| 곡 | 바뀐 것 | 정답과 비교 |
|---|---|---|
| 끝 후렴이 5/4로, 후렴마다 2박 마디가 끼던 4/4 곡(사용자 제보) | 박자 변화 없음, 길이가 다른 마디 27개 → 0, 조 이름 단조 → 장조 | 정답 파일 없음(되풀이·베이스 분석으로 4/4·장조 확인) |
| GTZAN hiphop.00067, reggae.00022, reggae.00077 | 길이가 다른 마디가 줄어듦 | 첫 박 F 0.56·0.89·0.81 → 1.00 |
| GTZAN country.00039, jazz.00074 | 조 이름 F♯ 단조 → A 장조, E 단조 → G 장조 | 틀림 → 맞음 |
| GTZAN blues.00061 | 조 이름 F 단조 → A♭ 장조 | 정답 G 단조, 전후 모두 틀림(MIREX 점수 같음) |
박·첫 박·코드·조성 가운데 나빠진 곡은 없습니다. 조성은 정답과의 거리(MIREX 가중 점수)가 줄어든 곡도 없게 했습니다(문턱을 0.1로 두면 정답의 5도 이웃 조에서 먼 조로 바뀌는 곡이 하나 있어 0.15로). 회귀 18곡·조성·로마 숫자 시험, 합성 곡 37곡 채점, 실제 녹음 11곡 키트, 「세월이 가면」 정답 시험, GuitarSet은 1.11.3과 같습니다.
조건 없이는 넣지 않은 것. 세 규칙을 조건 없이 쓰면 바뀌는 곡이 많았습니다. 박자 변화 확인 8곡(첫 박 3곡 나빠짐), 마디 다시 보기 159곡(첫 박 72곡 좋아짐, 34곡 나빠짐), 조 이름 118곡(조성 26곡 맞게, 29곡 틀리게)입니다. 기존 곡을 지키도록 근거가 뚜렷할 때만 쓰게 하면서 좋아지던 곡도 대부분 포기했습니다. 베이스가 근음이 아닌 m7만 장조로 세는 방법(76곡, 조성 17곡 맞게·13곡 틀리게)과, 조성 안 m7의 7음 베이스를 장3화음/베이스로 적는 이름 규칙(D♯m7/C♯ → F♯/C♯, 「바다의 왕자」 근음 99.3% → 99.1%)도 시험했지만 넣지 않았습니다.
분석 속도. 계산 결과는 한 비트도 바꾸지 않고(같은 연산을 같은 순서로) 분석을 약 1.8배 빠르게 했습니다. 표본율 변환은 같은 위상의 출력 8개를 나란히 계산하고, FFT는 두 단계를 한 번에 돌며 쓰는 주파수 칸만 구합니다. 크롬에서 실제 녹음 5곡의 분석과 차트 만들기에 걸린 시간 합이 20.2초에서 11.3초로 줄어(1.11.0에서 늘어난 몫을 되돌리고 1.10.5보다도 빠름), 분석 중 최대 메모리는 1.11.0과 같습니다. 같은 결과인지는 실제 녹음(모노·3채널·48 kHz 판 포함), GuitarSet 90곡, 합성 곡 74곡, 여러 표본율·길이·무음 입력과 꺼진 스위치 조합에서 엔진이 내는 모든 수를 비교해 확인했습니다.
조성 퇴행 바로잡기. 1.11.0에서 「고민중독」 조성이 B 단조 → E 단조 → F 단조로 잘못 적히게 됐습니다(1.10.5는 정답과 같은 G 장조 → A♭ 장조). 키트에 이 곡의 조성 정답 파일이 없어 1.11.0 시험에서 놓쳤습니다. 원인은 노래 선율 줄이기가 남긴 7화음 이름 하나(도입부의 C가 C7로) 때문에 도입부 조성이 점수 0.007 차이로 D 장조가 된 것이고, 거의 동점인 전조 거르기로 고쳤습니다. 이 규칙으로 바뀐 곳은 「고민중독」과 GTZAN 1000곡 중 힙합 1곡의 가짜 전조뿐이고(그 곡은 정답과 전후 모두 다름), 키트 10곡, GuitarSet 180곡, 합성 곡, 조성·로마 숫자 시험은 조성까지 그대로입니다.
성질 이름. 반감7 이름을 넣어 GuitarSet 연주 기준 성질이 59.37% → 60.28%가 됐고, 키트 10곡의 성질은 66.0% → 66.2%(「이 사람이다」·「もう恋なんてしない」의 m7♭5), 곡당 분석 시간은 같은 컴퓨터의 node에서 평균 4.0초 → 2.1초입니다. 근음·장단·박·조성은 모든 시험 묶음에서 그대로입니다.
조사했지만 넣지 않은 것.
1.11.1의 다른 시험 결과: 합성 곡 37곡과 처음 보는 합성 곡 37곡, 회귀 18곡·조성·로마 숫자 시험, Warak, GTZAN과 GuitarSet의 박·첫 박·근음·장단은 1.11.0과 같습니다. 「세월이 가면」 정답 시험은 근음 94.7%, 장·단 91.2%, 성질 78.1%로 그대로입니다.
| 항목 | 1.8.3 | 1.8.4 |
|---|---|---|
| 합성곡 마디 첫머리 회색(모두 새로 친 곳) | 49곳 | 8곳, 모두 곡이 끝난 뒤 마지막 코드의 여운 |
| 코드 바뀜이 정답과 같은 박 / 한 박 이름 / 한 박 늦음 / 정답에 없는 바뀜 | 772 / 50 / 19 / 35 | 776 / 46 / 19 / 35 |
| 회귀 18곡 코드 정확 / 계열 | 98.6% / 99.8% | 98.9% / 100% |
| 대중음악형 곡 코드 정확 / 계열 | 97.0% / 99.2% | 97.2% / 99.3% |
| 비트, 첫 박, 조성, 특수 장르, 로마 숫자 시험 | 같음 | |
| 실제 녹음 | 코드·첫 박·비트 같음. 회색 6곳 중 새로 친 1곳(24마디)만 검은 글자 | |
| 끌어 둔 코드가 섞인 패드 곡(같은 코드가 이어지는 8곳) | 7곳 맞음 |
화면에서 그리는 위치도 확인했습니다. 6곡을 데스크톱과 모바일 폭에서 열었을 때 코드 이름 306개가 모두 자기 박의 슬래시와 같은 거리에 있었습니다.
한계
앞으로 해 볼 만한 것
버전은 메이저.마이너.빌드 세 숫자로 적습니다. 지금 버전은 앱과 도움말의 제목 옆에 표시됩니다.
| 자리 | 올리는 때 | 예 |
|---|---|---|
| 메이저 | 앱의 이름이나 쓰는 방식이 크게 바뀔 때. 0은 정식 이름을 정하기 전의 개발판 | 1.0.0 이름을 Chordity로 정함 |
| 마이너 | 새 기능이 더해질 때. 빌드 숫자는 0부터 다시 셈 | 1.3.0 로마 숫자 표기 |
| 빌드 | 기능은 그대로이고 오류 수정, 소리나 수치 조정, 문구와 문서가 바뀔 때 | 1.4.1 안내 문구 다듬기 |
A♯m7 D♯m7). 첫 박에서 다른 코드가 뚜렷이 앞서고, 그 코드의 음을 첫 박에 새로 치고 둘째 박에 원래 코드를 다시 쳤으며, 두 코드의 베이스가 다를 때만 첫 박을 따로 적습니다. 앞 코드의 잔향이 섞여 다른 코드처럼 들리는 경우는 바꾸지 않습니다.N.C. 글자를 조금 진하게 해, 주황 채움 위에서도 읽히게 했습니다.N.C.는 상자 위와 바탕 위 어디서나 읽히도록 회색을 조금 조정했습니다.N.C. 자리는 보통 빗금입니다.N박 표시가 서로 겹치거나 줄 밖으로 나가지 않게 놓습니다. 알약끼리 가까우면 도착 알약이 조금 비켜 서고, 틀은 알약에 닿는 쪽 끝을 줄이며, 틀이 아주 짧아 여기까지 63이 다 들어가지 않으면 숫자만 적습니다.N박 표시의 바탕이 코드 이름 칸 위쪽에 걸치던 것을 고쳤습니다. 줄 끝 박의 알약이 줄 밖으로 나가 좁은 화면이 가로로 넘치거나, 줄 첫머리의 전조 알약이 윗줄 슬래시 아래 줄에 닿거나, 전조 알약이 N박 표시와 겹칠 수 있던 배치도 막았습니다./F♯)부터 빼고 …을 붙입니다.C 장조 → C 장조처럼 같은 조로 전조한 것으로 적히던 것을 고쳤습니다. 조성 구간을 나눈 뒤 코드로 조를 다시 정했을 때 이웃한 두 구간이 같은 조가 되면 한 구간으로 합칩니다.C 장조 4/4).로/으로를 붙입니다(D 장조로, 3/4으로, 132로, 63으로). 곡 시작점의 이름표에는 조사를 붙이지 않습니다.여기까지 63처럼 도착하는 템포를 적고, 재생 위치가 틀 안에 있으면 현재 67처럼 지금 템포를 보여 줍니다.4/4 → 3/4 → 4/4처럼 표시하고, 바뀌는 마디에 3/4로 이름표를 붙입니다.120)으로 적습니다.information.html)를 새로 만들었습니다. 결과가 틀릴 수 있는 경우와 확인하는 법, 분석 정보 읽는 법, 원리, 수식, 소리의 물리, 구조, 검증, 용어, 참고 문헌을 담았고, 목차와 찾기, 밝은·어두운 테마를 갖췄습니다.정식 이름을 정하기 전에는 “마디 코드”라는 이름으로 만들었습니다.
docs 폴더)을 정리했습니다.