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}