khora_agents/audio_agent/
mod.rs1use std::sync::Arc;
28use std::time::Duration;
29
30use khora_core::agent::{Agent, AgentImportance, ExecutionPhase, ExecutionTiming};
31use khora_core::audio::AudioMixBus;
32use khora_core::control::gorna::{
33 AgentId, AgentStatus, NegotiationRequest, NegotiationResponse, ResourceBudget, StrategyId,
34 StrategyOption,
35};
36use khora_core::lane::{LaneContext, LaneRegistry, Ref, Slot};
37use khora_core::EngineContext;
38use khora_data::flow::AudioView;
39use khora_lanes::audio_lane::SpatialMixingLane;
40
41pub struct AudioAgent {
43 lanes: LaneRegistry,
46 current_lane: &'static str,
49 current_strategy: StrategyId,
51 max_sources_per_frame: usize,
53}
54
55impl Default for AudioAgent {
56 fn default() -> Self {
57 let mut lanes = LaneRegistry::new();
58 lanes.register(Box::new(SpatialMixingLane::new()));
59
60 Self {
61 lanes,
62 current_lane: "SpatialMixing",
63 current_strategy: StrategyId::Balanced,
64 max_sources_per_frame: 32,
65 }
66 }
67}
68
69impl Agent for AudioAgent {
70 fn id(&self) -> AgentId {
71 AgentId::Audio
72 }
73
74 fn negotiate(&mut self, _request: NegotiationRequest) -> NegotiationResponse {
75 NegotiationResponse {
76 strategies: vec![
77 StrategyOption {
78 id: StrategyId::LowPower,
79 estimated_time: Duration::from_micros(100),
80 estimated_vram: 0,
81 },
82 StrategyOption {
83 id: StrategyId::Balanced,
84 estimated_time: Duration::from_micros(500),
85 estimated_vram: 0,
86 },
87 StrategyOption {
88 id: StrategyId::HighPerformance,
89 estimated_time: Duration::from_micros(2000),
90 estimated_vram: 0,
91 },
92 ],
93 timing_adjustment: None,
94 }
95 }
96
97 fn apply_budget(&mut self, budget: ResourceBudget) {
98 log::info!("AudioAgent: Strategy update to {:?}", budget.strategy_id);
99
100 self.current_strategy = budget.strategy_id;
101 self.max_sources_per_frame = match budget.strategy_id {
102 StrategyId::LowPower => 8,
103 StrategyId::Balanced => 32,
104 StrategyId::HighPerformance => 128,
105 StrategyId::Custom(n) => n as usize,
106 };
107 }
108
109 fn on_initialize(&mut self, _context: &mut EngineContext<'_>) {
110 let mut init_ctx = LaneContext::new();
111 for lane in self.lanes.all() {
112 if let Err(e) = lane.on_initialize(&mut init_ctx) {
113 log::error!(
114 "AudioAgent: Failed to initialize lane {}: {}",
115 lane.strategy_name(),
116 e
117 );
118 }
119 }
120
121 log::info!("AudioAgent: Initialized with {} lanes", self.lanes.len());
122 }
123
124 fn execute(&mut self, context: &mut EngineContext<'_>) {
125 let Some(mix_bus) = context
126 .runtime
127 .resources
128 .get::<Arc<dyn AudioMixBus>>()
129 .cloned()
130 else {
131 log::debug!("AudioAgent: no AudioMixBus in resources, skipping mix");
132 return;
133 };
134
135 let Some(view): Option<&AudioView> = context.bus.get() else {
136 return;
138 };
139
140 let mut ctx = LaneContext::new();
141 ctx.insert(mix_bus);
142 ctx.insert(Ref::new(view));
146 ctx.insert(Slot::new(&mut *context.deck));
150
151 if let Some(lane) = self.lanes.get(self.current_lane) {
152 if let Err(e) = lane.execute(&mut ctx) {
153 log::error!("Audio lane {} failed: {}", lane.strategy_name(), e);
154 }
155 }
156 }
157
158 fn report_status(&self) -> AgentStatus {
159 AgentStatus {
160 agent_id: self.id(),
161 health_score: 1.0,
162 current_strategy: self.current_strategy,
163 is_stalled: false,
164 message: format!("max_sources={}", self.max_sources_per_frame),
165 }
166 }
167
168 fn as_any(&self) -> &dyn std::any::Any {
169 self
170 }
171
172 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
173 self
174 }
175
176 fn execution_timing(&self) -> ExecutionTiming {
177 ExecutionTiming {
178 allowed_phases: vec![ExecutionPhase::TRANSFORM],
179 default_phase: ExecutionPhase::TRANSFORM,
180 priority: 0.5,
181 importance: AgentImportance::Important,
182 fixed_timestep: None,
183 dependencies: Vec::new(),
184 }
185 }
186}