code wiki / _hdl_build / nx_skill_coach_audio.nx

nx_skill_coach_audio.nx source

↩ module page · 69 lines · 4310 B

1// nx_skill_coach_audio.nx -- SKILL-COACH (singing/instrument) audio vertical, rung R1: the COACH LOOP that turns a 2// detected pitch into ACTIONABLE FEEDBACK (the operator's ask: "feedback on what exercises/training to get skills 3// up"). Verify-don't-rebuild: the hard PERCEPTION already exists + is gated (nx_pitch YIN-F0 -> PCM, nx_note 12-TET 4// cents); this composes nx_note into GRADE -> DIAGNOSE -> recommend a DRILL. The shared coach ENGINE + rubric-store 5// + gamification + /coach publish are the team-build spec (knowledge/specs, routed to the team). NO floats. 6// Self-gate: 5 known pitches vs target A4 -> correct verdict + drill. license_tier: ORIGINAL 7import "nx_note.nx" 8import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 9import "nx_syscalls.nx" 10 11const TOL_CENTS: i64 = 20 // |cents| <= TOL = "in tune" (data-driven threshold; R2 -> rubric store, rule #11) 12 13func sc_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 14// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 15// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 16// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 17// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 18func sc_putn(v: i64) -> i64 { nxi_out(v); return 0 } 19// GRADE a sung F0 vs a target note. verdict: 0 IN_TUNE, 1 SHARP, 2 FLAT, 3 NOTE-TOO-HIGH, 4 NOTE-TOO-LOW. 20// (cents into cout[0], detected midi into mout[0] -- the measured signal a gamified score reads.) 21func coach_grade(sung_f0: i64, target_midi: i64, tbl: *i64, cout: *i64, mout: *i64) -> i64 { 22 let no: *i64 = sys_mmap(8 * 4) as *i64 23 nx_note_of(sung_f0, tbl, no) 24 let dm: i64 = no[0] 25 let cents: i64 = no[1] 26 cout[0] = cents 27 mout[0] = dm 28 if dm == target_midi { 29 if cents > TOL_CENTS { return 1 } 30 if cents < (0 - TOL_CENTS) { return 2 } 31 return 0 32 } 33 if dm > target_midi { return 3 } 34 return 4 35} 36// DIAGNOSE -> DRILL: the data-driven rubric (verdict -> what to practice). R1 = inline; R2 = a seg_store rubric 37// (rule #11/#25 data-driven, edit-here) so the curriculum is configurable per discipline/voice-type, not hardcoded. 38func coach_drill(verdict: i64) -> *u8 { 39 if verdict == 0 { return "ON PITCH -> advance: next note / wider interval" as *u8 } 40 if verdict == 1 { return "SHARP -> relax throat, lower slightly; DRILL: sustained-tone pitch-matching" as *u8 } 41 if verdict == 2 { return "FLAT -> add breath support; DRILL: breath-support long-tones" as *u8 } 42 if verdict == 3 { return "WRONG NOTE (too high) -> DRILL: descending-interval ear-training" as *u8 } 43 return "WRONG NOTE (too low) -> DRILL: ascending-interval ear-training" as *u8 44} 45// one coached attempt: print the full feedback line 46func sc_attempt(f0: i64, target_midi: i64, tbl: *i64, cout: *i64, mout: *i64) -> i64 { 47 let v: i64 = coach_grade(f0, target_midi, tbl, cout, mout) 48 sc_puts(" sung "); sc_putn(f0); sc_puts("Hz -> midi "); sc_putn(mout[0]) 49 sc_puts(" ("); sc_putn(cout[0]); sc_puts("c) -> "); sc_puts(coach_drill(v)); sc_puts("\n" as *u8) 50 return v 51} 52func main() -> i64 { 53 sc_puts("SKILL-COACH (audio R1): pitch -> GRADE -> DIAGNOSE -> DRILL (target = A4 / midi 69)\n" as *u8) 54 let tbl: *i64 = sys_mmap(8 * 128) as *i64 55 nx_note_table(tbl) 56 let cout: *i64 = sys_mmap(16) as *i64 57 let mout: *i64 = sys_mmap(16) as *i64 58 var pass: i64 = 0 59 // (f0, expected verdict): 440 in-tune, 450 sharp, 430 flat, 494 (B4) too-high, 392 (G4) too-low 60 if sc_attempt(440, 69, tbl, cout, mout) == 0 { pass = pass + 1 } 61 if sc_attempt(450, 69, tbl, cout, mout) == 1 { pass = pass + 1 } 62 if sc_attempt(430, 69, tbl, cout, mout) == 2 { pass = pass + 1 } 63 if sc_attempt(494, 69, tbl, cout, mout) == 3 { pass = pass + 1 } 64 if sc_attempt(392, 69, tbl, cout, mout) == 4 { pass = pass + 1 } 65 sc_puts(" coach verdicts correct = "); sc_putn(pass); sc_puts("/5\n" as *u8) 66 if pass == 5 { sc_puts("SKILL-COACH-AUDIO: GREEN -- the listen->grade->diagnose->drill loop works on the gated perception; this is the audio-coach core\n" as *u8); return 0 } 67 sc_puts("SKILL-COACH-AUDIO: RED\n" as *u8) 68 return 1 69}