code wiki / (root) / nx_note.nx

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}