code wiki / _hdl_build / nx_skill_coach_chord.nx

nx_skill_coach_chord.nx source

↩ module page · 99 lines · 5129 B

1// nx_skill_coach_chord.nx -- SKILL-COACH instrument vertical, rung R3 (guitar/bass/piano chords). Composes the 2// gated nx_polypitch contract (it returns detected MIDI notes -> out_notes) the same way R1 composed nx_note: 3// detected chord -> GRADE vs a target chord -> DIAGNOSE the missing/extra note -> recommend a DRILL. Compared by 4// PITCH CLASS (mod 12) so voicing/octave don't matter. Rubric inline at R3; R2 moves it to a seg_store (rule#11). 5// Self-gate: 4 known chord cases vs target C major. NO floats. license_tier: ORIGINAL 6import "nx_syscalls.nx" 7import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 8 9func cc_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 10// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 11// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 12// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 13// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 14func cc_putn(v: i64) -> i64 { nxi_out(v); return 0 } 15// pitch-class name (0=C..11=B) 16func cc_pcname(pc: i64) -> *u8 { 17 if pc == 0 { return "C" as *u8 } 18 if pc == 1 { return "C#" as *u8 } 19 if pc == 2 { return "D" as *u8 } 20 if pc == 3 { return "D#" as *u8 } 21 if pc == 4 { return "E" as *u8 } 22 if pc == 5 { return "F" as *u8 } 23 if pc == 6 { return "F#" as *u8 } 24 if pc == 7 { return "G" as *u8 } 25 if pc == 8 { return "G#" as *u8 } 26 if pc == 9 { return "A" as *u8 } 27 if pc == 10 { return "A#" as *u8 } 28 return "B" as *u8 29} 30// detected MIDI notes (nx_polypitch out_notes) -> 12-bit pitch-class mask 31func cc_mask(notes: *i64, n: i64) -> i64 { 32 var mask: i64 = 0 33 var i: i64 = 0 34 while i < n { mask = mask | (1 << (notes[i] % 12)); i = i + 1 } 35 return mask 36} 37// print the pitch-class names present in a 12-bit mask 38func cc_print_mask(mask: i64) -> i64 { 39 var pc: i64 = 0 40 while pc < 12 { 41 if (mask & (1 << pc)) != 0 { cc_puts(cc_pcname(pc)); cc_puts(" " as *u8) } 42 pc = pc + 1 43 } 44 return 0 45} 46// GRADE detected vs target (both 12-bit pc masks). verdict: 0 CLEAN, 1 MISSING-only, 2 EXTRA-only, 3 WRONG(both). 47// missing -> mout[0], extra -> eout[0]. 48func cc_grade(detected: i64, target: i64, mout: *i64, eout: *i64) -> i64 { 49 let missing: i64 = target & (0xfff ^ detected) 50 let extra: i64 = detected & (0xfff ^ target) 51 mout[0] = missing 52 eout[0] = extra 53 if missing == 0 { if extra == 0 { return 0 } } 54 if missing != 0 { if extra == 0 { return 1 } } 55 if extra != 0 { if missing == 0 { return 2 } } 56 return 3 57} 58func cc_drill(verdict: i64, missing: i64, extra: i64) -> i64 { 59 if verdict == 0 { cc_puts("CLEAN chord -> advance: next chord / faster changes\n" as *u8); return 0 } 60 if verdict == 1 { 61 cc_puts("MISSING "); cc_print_mask(missing); cc_puts("-> DRILL: chord-tone isolation (fret each string, find the dead/missing finger)\n" as *u8); return 0 62 } 63 if verdict == 2 { 64 cc_puts("EXTRA "); cc_print_mask(extra); cc_puts("(open/muted string ringing?) -> DRILL: clean-press + string muting\n" as *u8); return 0 65 } 66 cc_puts("WRONG QUALITY (missing "); cc_print_mask(missing); cc_puts(", extra "); cc_print_mask(extra) 67 cc_puts(") -> DRILL: chord-shape correction (e.g. minor vs major = the 3rd)\n" as *u8); return 0 68} 69// one coached attempt 70func cc_attempt(notes: *i64, n: i64, target: i64) -> i64 { 71 let det: i64 = cc_mask(notes, n) 72 let mo: *i64 = sys_mmap(16) as *i64 73 let eo: *i64 = sys_mmap(16) as *i64 74 let v: i64 = cc_grade(det, target, mo, eo) 75 cc_puts(" played [ "); cc_print_mask(det); cc_puts("] -> ") 76 cc_drill(v, mo[0], eo[0]) 77 return v 78} 79func main() -> i64 { 80 cc_puts("SKILL-COACH (instrument R3): detected chord -> GRADE -> DIAGNOSE -> DRILL (target = C major {C,E,G})\n" as *u8) 81 let target: i64 = (1 << 0) | (1 << 4) | (1 << 7) // C major = C,E,G 82 var pass: i64 = 0 83 // 1: clean C major [60,64,67] 84 let n1: *i64 = sys_mmap(8 * 4) as *i64; n1[0] = 60; n1[1] = 64; n1[2] = 67 85 if cc_attempt(n1, 3, target) == 0 { pass = pass + 1 } 86 // 2: missing the 3rd [60,67] -> MISSING E 87 let n2: *i64 = sys_mmap(8 * 4) as *i64; n2[0] = 60; n2[1] = 67 88 if cc_attempt(n2, 2, target) == 1 { pass = pass + 1 } 89 // 3: minor 3rd instead [60,63,67] (C,D#,G) -> WRONG (missing E, extra D#) 90 let n3: *i64 = sys_mmap(8 * 4) as *i64; n3[0] = 60; n3[1] = 63; n3[2] = 67 91 if cc_attempt(n3, 3, target) == 3 { pass = pass + 1 } 92 // 4: extra ringing string [60,64,67,70] (C,E,G,A#) -> EXTRA A# 93 let n4: *i64 = sys_mmap(8 * 4) as *i64; n4[0] = 60; n4[1] = 64; n4[2] = 67; n4[3] = 70 94 if cc_attempt(n4, 4, target) == 2 { pass = pass + 1 } 95 cc_puts(" chord verdicts correct = "); cc_putn(pass); cc_puts("/4\n" as *u8) 96 if pass == 4 { cc_puts("SKILL-COACH-CHORD: GREEN -- chord grade/diagnose/drill on the nx_polypitch contract; guitar/bass coach core\n" as *u8); return 0 } 97 cc_puts("SKILL-COACH-CHORD: RED\n" as *u8) 98 return 1 99}