nx_actor_role_audio_grader.nx source
↩ module page · 183 lines · 6865 B
1// nx_actor_role_audio_grader.nx -- audio-scene-grading actor role.
2//
3// Per CARDINAL [[feedback-parallel-companion-multimodal-dnd-real-time]]:
4// the parallel companion runs DIFFERENT modalities concurrently --
5// LLM token gen + audio grading + image grading all interleaved. This
6// adapter wraps [[nx_audio_scene_quality]] (other-agent primitive) as
7// a cooperative actor so the substrate can drive heterogeneous work,
8// not just LLM clones.
9//
10// Cooperative phases:
11// Phase 0 (INIT): no-op (reserved for future warmup)
12// Phase 1 (GRADE): calls nx_audio_scene_grade with caller bins/rms/n_sources
13// Phase 2 (EMIT): pushes AUDIO_FRAME message containing the verdict
14// overall_score (Q14) as the payload handle
15// Phase 3 (DONE): terminal
16//
17// Composes [[project-conductor-arc-phases-c-d-integrate-with-nishi-ai-
18// 2026-05-19]] -- audio grading runs through the same actor + message +
19// multiplexer substrate as LLM inference.
20
21import "nx_syscalls.nx"
22import "nx_tier.nx"
23import "nx_layer_verdict.nx"
24import "nx_audio_scene_quality.nx"
25import "nx_actor.nx"
26import "nx_message.nx"
27
28// ===== Sealed enum: NxAudioGraderPhase ============================
29
30const NX_AG_PHASE_INIT: nx_int = 0
31const NX_AG_PHASE_GRADE: nx_int = 1
32const NX_AG_PHASE_EMIT: nx_int = 2
33const NX_AG_PHASE_DONE: nx_int = 3
34const NX_AG_PHASE_N: nx_int = 4
35
36// ===== Sealed enum: NxAudioGraderVerdict ==========================
37
38const NX_AG_V_STEPPED: nx_int = 0
39const NX_AG_V_COMPLETED: nx_int = 1
40const NX_AG_V_FAILED_GRADE: nx_int = 2
41const NX_AG_V_INVALID: nx_int = 3
42const NX_AG_V_NULL: nx_int = 4
43const NX_AG_V_N: nx_int = 5
44
45// ===== Struct: NxAudioGraderCtx ===================================
46//
47// Caller owns spectral_bins + rms_window storage; adapter owns verdict.
48
49struct NxAudioGraderCtx {
50 spectral_bins: *i64,
51 n_bins: nx_int,
52 rms_window: *i64,
53 n_frames: nx_int,
54 n_sources: nx_int,
55 verdict: *i64, // NX_LV_STRIDE i64s
56 overall_score_q14: nx_int,
57 current_phase: nx_int,
58 last_step_verdict: nx_int,
59}
60
61const NX_AG_CTX_BYTES: nx_int = 72 // 9 fields * 8
62
63// ===== Validators =================================================
64
65func nx_ag_phase_is_valid(p: nx_int) -> nx_int {
66 if p < 0 { return 0 }
67 if p >= NX_AG_PHASE_N { return 0 }
68 return 1
69}
70
71func nx_ag_v_is_valid(v: nx_int) -> nx_int {
72 if v < 0 { return 0 }
73 if v >= NX_AG_V_N { return 0 }
74 return 1
75}
76
77// ===== Constructor ================================================
78
79func nx_ag_actor_new(spectral_bins: *i64,
80 n_bins: nx_int,
81 rms_window: *i64,
82 n_frames: nx_int,
83 n_sources: nx_int) -> *NxAudioGraderCtx {
84 if (spectral_bins as i64) == 0 { return 0 as *NxAudioGraderCtx }
85 if (rms_window as i64) == 0 { return 0 as *NxAudioGraderCtx }
86 if n_bins <= 0 { return 0 as *NxAudioGraderCtx }
87 if n_frames <= 0 { return 0 as *NxAudioGraderCtx }
88 if n_sources < 0 { return 0 as *NxAudioGraderCtx }
89 let raw: *u8 = sys_mmap(NX_AG_CTX_BYTES)
90 let ctx: *NxAudioGraderCtx = raw as *NxAudioGraderCtx
91 ctx.spectral_bins = spectral_bins
92 ctx.n_bins = n_bins
93 ctx.rms_window = rms_window
94 ctx.n_frames = n_frames
95 ctx.n_sources = n_sources
96 ctx.verdict = sys_mmap(NX_LV_STRIDE * 8) as *i64
97 ctx.overall_score_q14 = 0
98 ctx.current_phase = NX_AG_PHASE_INIT
99 ctx.last_step_verdict = NX_AG_V_STEPPED
100 return ctx
101}
102
103// ===== Phase helpers ==============================================
104
105func _ag_step_grade(ctx: *NxAudioGraderCtx) -> nx_int {
106 nx_audio_scene_grade(ctx.spectral_bins, ctx.n_bins,
107 ctx.rms_window, ctx.n_frames,
108 ctx.n_sources, ctx.verdict)
109 // The verdict layout puts the overall pass/score at NX_LV_OFF_OVERALL;
110 // we use the AVG of the 5 axes as an honest summary since the layer
111 // verdict's overall is a refine-signal, not a pass score.
112 var sum: nx_int = 0
113 var i: nx_int = 0
114 while i < NX_AQ_AXIS_COUNT {
115 sum = sum + (ctx.verdict[NX_LV_OFF_AXIS_0 + i] as nx_int)
116 i = i + 1
117 }
118 ctx.overall_score_q14 = sum / NX_AQ_AXIS_COUNT
119 if ctx.overall_score_q14 < 0 { return NX_AG_V_FAILED_GRADE }
120 return NX_AG_V_STEPPED
121}
122
123func _ag_step_emit(ctx: *NxAudioGraderCtx,
124 bus: *NxMessageBus,
125 sender_actor_id: nx_int,
126 now_us: nx_size) -> nx_int {
127 if (bus as i64) == 0 { return NX_AG_V_STEPPED }
128 nx_ms_send_fanout(bus, sender_actor_id, NX_MS_KIND_AUDIO_FRAME,
129 ctx.overall_score_q14 as nx_size, 8, now_us)
130 return NX_AG_V_STEPPED
131}
132
133// ===== Public step ================================================
134
135func nx_ag_actor_step(ctx: *NxAudioGraderCtx,
136 sched: *NxActorScheduler,
137 bus: *NxMessageBus,
138 actor_id: nx_int,
139 now_us: nx_size) -> nx_int {
140 if (ctx as i64) == 0 { return NX_AG_V_NULL }
141 if ctx.current_phase >= NX_AG_PHASE_DONE { return NX_AG_V_COMPLETED }
142 var v: nx_int = NX_AG_V_STEPPED
143 if ctx.current_phase == NX_AG_PHASE_INIT { v = NX_AG_V_STEPPED }
144 if ctx.current_phase == NX_AG_PHASE_GRADE { v = _ag_step_grade(ctx) }
145 if ctx.current_phase == NX_AG_PHASE_EMIT { v = _ag_step_emit(ctx, bus, actor_id, now_us) }
146 ctx.last_step_verdict = v
147 ctx.current_phase = ctx.current_phase + 1
148 if (sched as i64) != 0 { nx_ac_step(sched, actor_id, 1000, now_us) }
149 if ctx.current_phase >= NX_AG_PHASE_DONE {
150 if (sched as i64) != 0 { nx_ac_complete(sched, actor_id) }
151 return NX_AG_V_COMPLETED
152 }
153 if v != NX_AG_V_STEPPED {
154 if (sched as i64) != 0 { nx_ac_fail(sched, actor_id) }
155 return v
156 }
157 return NX_AG_V_STEPPED
158}
159
160// ===== Accessors ==================================================
161
162func nx_ag_actor_phase(ctx: *NxAudioGraderCtx) -> nx_int {
163 if (ctx as i64) == 0 { return NX_AG_PHASE_DONE }
164 return ctx.current_phase
165}
166
167func nx_ag_actor_score_q14(ctx: *NxAudioGraderCtx) -> nx_int {
168 if (ctx as i64) == 0 { return 0 }
169 return ctx.overall_score_q14
170}
171
172func nx_ag_actor_is_done(ctx: *NxAudioGraderCtx) -> nx_int {
173 if (ctx as i64) == 0 { return 0 }
174 if ctx.current_phase >= NX_AG_PHASE_DONE { return 1 }
175 return 0
176}
177
178func nx_ag_actor_axis_q14(ctx: *NxAudioGraderCtx, axis: nx_int) -> nx_int {
179 if (ctx as i64) == 0 { return 0 }
180 if axis < 0 { return 0 }
181 if axis >= NX_AQ_AXIS_COUNT { return 0 }
182 return ctx.verdict[NX_LV_OFF_AXIS_0 + axis] as nx_int
183}