아두이노 tone()이나 Windows의 Beep API로 멜로디를 낼 때는 "도"가 몇 Hz인지 숫자로 넣어야 합니다. 매번 계산하기 번거로워서 계이름별 주파수를 표로 만들어 두었습니다.
소리의 높낮이는 주파수로 정해진다
소리는 공기의 진동이고, 1초에 몇 번 떨리느냐(주파수, Hz)에 따라 음의 높이(피치)가 달라집니다. 사람 귀로 들을 수 있는 범위는 대략 20Hz에서 20,000Hz 사이로 알려져 있습니다. 실제 부저나 작은 스피커는 이 범위 전체를 고르게 내지 못하니, 너무 낮거나 높은 음은 소리가 작게 나거나 거의 안 들릴 수 있습니다.
음계의 기준은 피아노 "라(A)" 음 440Hz입니다. 한 옥타브를 올라가면 주파수가 정확히 두 배가 되고, 그 사이를 12개의 반음(A, A#, B, C, C#, D, D#, E, F, F#, G, G#)으로 나누는데, 이때 같은 간격으로 더하는 게 아니라 같은 비율로 곱해 나갑니다(등비수열). 반음 하나 올라갈 때마다 2의 12제곱근, 약 1.0595배씩 커지는 구조입니다.

그래서 표가 없어도 기준음에서 몇 반음 떨어졌는지만 알면 직접 구할 수 있습니다.
주파수 = 440 × 2^(n / 12) ' n = A4(440Hz)에서 떨어진 반음 수 (위로 +, 아래로 -)
예) C5 (A4 에서 위로 3반음) = 440 × 2^(3/12) ≈ 523.25
C4 (A4 에서 아래로 9반음) = 440 × 2^(-9/12) ≈ 261.63
MIDI를 다뤄 봤다면 음 번호로 계산하는 쪽이 더 편할 수 있습니다. MIDI 음 번호에서 A4는 69, 가운데 도(C4)는 60이라서 440 × 2^((번호 - 69) / 12)로 바로 주파수가 나옵니다. 코드로 옮기면 C#에서는 440 * Math.Pow(2, (note - 69) / 12.0) 한 줄이고, 여기서 12.0처럼 실수로 나눠야 합니다. 정수끼리 나누면 소수점이 버려져서 반음 단위가 다 무너집니다.
옥타브별 주파수 표 (단위 Hz)
빈칸 없이 반음까지 모두 넣었습니다. 도(C)·레(D)·미(E)·파(F)·솔(G)·라(A)·시(B)가 흰 건반, #이 붙은 음이 검은 건반입니다.
| 옥타브 | C 도 | C# | D 레 | D# | E 미 | F 파 | F# | G 솔 | G# | A 라 | A# | B 시 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 32.7032 | 34.64783 | 36.7081 | 38.89087 | 41.20344 | 43.65353 | 46.2493 | 48.99943 | 51.91309 | 55 | 58.27047 | 61.73541 |
| 2 | 65.40639 | 69.29566 | 73.41619 | 77.78175 | 82.40689 | 87.30706 | 92.49861 | 97.99886 | 103.8262 | 110 | 116.5409 | 123.4708 |
| 3 | 130.8128 | 138.5913 | 146.8324 | 155.5635 | 164.8138 | 174.6141 | 184.9972 | 195.9977 | 207.6523 | 220 | 233.0819 | 246.9417 |
| 4 | 261.6256 | 277.1826 | 293.6648 | 311.127 | 329.6276 | 349.2282 | 369.9944 | 391.9954 | 415.3047 | 440 | 466.1638 | 493.8833 |
| 5 | 523.2511 | 554.3653 | 587.3295 | 622.254 | 659.2551 | 698.4565 | 739.9888 | 783.9909 | 830.6094 | 880 | 932.3275 | 987.7666 |
| 6 | 1046.502 | 1108.731 | 1174.659 | 1244.508 | 1318.51 | 1396.913 | 1479.978 | 1567.982 | 1661.219 | 1760 | 1864.655 | 1975.533 |
| 7 | 2093.005 | 2217.461 | 2349.318 | 2489.016 | 2637.02 | 2793.826 | 2959.955 | 3135.963 | 3322.438 | 3520 | 3729.31 | 3951.066 |
| 8 | 4186.009 | 4434.922 | 4698.636 | 4978.032 | 5274.041 | 5587.652 | 5919.911 | 6271.927 | 6644.875 | 7040 | 7458.62 | 7902.133 |
같은 열에서 한 칸 내려갈 때마다 값이 두 배가 되는 걸 보면 표가 맞는지 쉽게 확인할 수 있습니다. A 열이 55, 110, 220, 440, 880으로 딱 떨어지는 것도 그 때문이고요. 4옥타브 C(261.6Hz)가 흔히 말하는 "가운데 도"입니다.
코드에 넣을 때
tone()이나 Beep는 주파수를 정수로 받는 경우가 많아서, 표의 값을 반올림해 넣으면 됩니다. 소수점 아래 차이는 귀로 구분하기 어렵습니다. 멜로디를 만들 때는 음 이름별 상수를 배열로 정의해 두고, 박자 길이 배열을 따로 두어 둘을 같이 돌리는 구조가 관리하기 편합니다. 음과 음 사이에 아주 짧은 쉼을 넣지 않으면 같은 음이 연속될 때 하나로 이어져 들리니, 그 부분도 같이 신경 쓰면 훨씬 자연스럽게 들립니다.
아래는 4옥타브 도부터 5옥타브 도까지 한 번 올라가는 아두이노 예제입니다. 8번 핀에 수동 부저(피에조)를 연결했다고 보고, 음 길이의 30% 정도를 쉼으로 둬서 음끼리 붙지 않게 했습니다.
const int BUZZER = 8;
int melody[] = {262, 294, 330, 349, 392, 440, 494, 523};
int durations[] = {300, 300, 300, 300, 300, 300, 300, 600};
void setup() {
for (int i = 0; i < 8; i++) {
tone(BUZZER, melody[i], durations[i]);
delay(durations[i] * 1.3);
noTone(BUZZER);
}
}
void loop() {}
부저를 살 때 "능동(액티브)" 부저인지 "수동(패시브)" 부저인지 확인하세요. 능동 부저는 전원만 주면 정해진 한 가지 소리만 내기 때문에 tone()으로 주파수를 바꿔도 음정이 거의 안 바뀝니다. 멜로디를 내려면 수동 부저가 필요합니다.
낼 수 있는 주파수에도 하한이 있습니다. 아두이노 우노 같은 AVR 보드의 tone()은 31Hz가 최저이고, .NET의 Console.Beep은 37Hz부터 받습니다. 그래서 표의 1옥타브 C·C#(32.7Hz, 34.6Hz)은 Console.Beep으로는 넣을 수 없고, 넣으면 범위 밖이라 예외가 납니다. 또 우노에서 tone()을 쓰는 동안은 3번·11번 핀의 PWM 출력이 영향을 받으니, LED 밝기 조절 같은 걸 같이 돌린다면 핀 배치를 먼저 확인해 두는 게 좋습니다.