nx_note.nx source
↩ module page · 70 lines · 2559 B
1// nx_note.nx -- AUDIO arc, INSTRUMENT-LEARN-GAME (Rocksmith-class) rung LG-0:
2// turn a detected pitch into a MUSICAL NOTE. This is the bedrock of "what note
3// are you playing?" for ANY instrument -- guitar, bass, piano, violin, voice --
4// because every pitched instrument lands on the same 12-tone-equal-temperament
5// grid. Composes nx_pitch (F0) upstream.
6//
7// 12-TET: MIDI note m has frequency f(m) = 440 * 2^((m-69)/12). A4 = 440 Hz =
8// MIDI 69. We build the note->frequency table by repeated multiply/divide by the
9// semitone ratio 2^(1/12) (Q16), so no runtime log/pow is needed; nearest-note is
10// a scan, and the in-semitone error is reported in cents (first-order, exact sign,
11// ~1-cent accurate within a semitone).
12//
13// Pure integer, NO floats, NO syscalls, NO imports (caller owns all buffers).
14//
15// nx_note_table(tbl) -- fill tbl[0..127] with note freq in centi-Hz
16// nx_note_of(f0_hz, tbl, out) -- out[0]=midi out[1]=cents out[2]=pitch_class
17// (0=C..11=B) out[3]=octave (MIDI 60 = C4);
18// returns midi, or -1 if f0<=0
19//
20// license_tier: ORIGINAL
21// module: nishi-core.audio.note
22// depends: nishi-core.audio.pitch
23// capability: AUDIO_NOTE_ID
24
25const NOTE_R12_Q16: i64 = 69433 // 2^(1/12) in Q16.16
26const NOTE_A4_MIDI: i64 = 69
27const NOTE_A4_CHZ: i64 = 44000 // 440.00 Hz in centi-Hz
28const NOTE_CENTS_K: i64 = 1731 // round(1200/ln2)
29
30// fill tbl[0..127] = note frequency in centi-Hz (Hz*100)
31func nx_note_table(tbl: *i64) -> i64 {
32 tbl[NOTE_A4_MIDI] = NOTE_A4_CHZ
33 var m: i64 = NOTE_A4_MIDI + 1
34 while m < 128 {
35 tbl[m] = (tbl[m - 1] * NOTE_R12_Q16) >> 16
36 m = m + 1
37 }
38 m = NOTE_A4_MIDI - 1
39 while m >= 0 {
40 tbl[m] = (tbl[m + 1] << 16) / NOTE_R12_Q16
41 m = m - 1
42 }
43 return 0
44}
45
46func nx_note_of(f0_hz: i64, tbl: *i64, out: *i64) -> i64 {
47 if f0_hz <= 0 {
48 out[0] = 0 - 1
49 out[1] = 0
50 out[2] = 0 - 1
51 out[3] = 0
52 return 0 - 1
53 }
54 let f_chz: i64 = f0_hz * 100
55 var best: i64 = 0
56 var bestd: i64 = 0x7fffffffffffffff
57 var m: i64 = 0
58 while m < 128 {
59 var d: i64 = f_chz - tbl[m]
60 if d < 0 { d = 0 - d }
61 if d < bestd { bestd = d; best = m }
62 m = m + 1
63 }
64 let cents: i64 = NOTE_CENTS_K * (f_chz - tbl[best]) / tbl[best]
65 out[0] = best
66 out[1] = cents
67 out[2] = best % 12 // 0=C, MIDI 60%12=0=C
68 out[3] = best / 12 - 1 // MIDI 60 -> octave 4 (C4)
69 return best
70}