1use crate::{
2 hw::traits::{HwMidiHub, HwWorkerDriver},
3 message::{HwMidiEvent, Message},
4};
5#[cfg(unix)]
6use nix::libc;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::time::Duration;
10use tokio::sync::mpsc::{Receiver, Sender};
11use tracing::error;
12
13pub trait Backend: Send + Sync + 'static {
14 type Driver: HwWorkerDriver + Send + 'static;
15 type MidiHub: HwMidiHub + Send + 'static;
16
17 const LABEL: &'static str;
18 const WORKER_THREAD_NAME: &'static str;
19 const ASSIST_THREAD_NAME: &'static str;
20 const ASSIST_AUTONOMOUS_ENV: &'static str;
21 const ASSIST_AUTONOMOUS_DEFAULT: bool = false;
22 const CYCLE_ON_WORKER_WHEN_ASSIST_AUTONOMOUS: bool = false;
23 const ASSIST_STEP_REQUIRES_REQUEST_CYCLE: bool = false;
24}
25
26#[derive(Debug)]
27pub struct HwWorker<B: Backend> {
28 driver: Option<B::Driver>,
32 midi_hub: B::MidiHub,
33 rx: Receiver<Message>,
34 tx: Sender<Message>,
35 cycle_frames: u32,
36 pending_midi_out_events: Vec<HwMidiEvent>,
37 pending_midi_out_sorted: bool,
38 midi_stop: Arc<AtomicBool>,
39 playing: bool,
43}
44
45const MIDI_INPUT_POLL_INTERVAL: Duration = Duration::from_millis(10);
49
50impl<B: Backend> Drop for HwWorker<B> {
51 fn drop(&mut self) {
52 if let Some(driver) = self.driver.as_mut() {
53 driver.request_stop();
54 }
55 self.midi_stop.store(true, Ordering::Release);
56 self.midi_hub.wake_input_waiter();
57 self.midi_hub.close_all();
58 if let Some(driver) = self.driver.as_mut() {
59 driver.close_fds();
60 }
61 }
62}
63
64#[cfg(unix)]
65const RT_POLICY: i32 = libc::SCHED_FIFO;
66const RT_PRIORITY_WORKER: i32 = 18;
67
68impl<B: Backend> HwWorker<B> {
69 fn configure_rt_thread(name: &str, priority: i32) -> Result<(), String> {
70 #[cfg(unix)]
71 {
72 let thread = unsafe { libc::pthread_self() };
73 #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd"))]
74 let c_name = std::ffi::CString::new(name).map_err(|e| e.to_string())?;
75 #[cfg(target_os = "linux")]
76 unsafe {
77 let _ = libc::pthread_setname_np(thread, c_name.as_ptr());
78 }
79 #[cfg(any(target_os = "freebsd", target_os = "openbsd"))]
80 unsafe {
81 libc::pthread_set_name_np(thread, c_name.as_ptr());
82 }
83
84 let param = unsafe {
85 let mut p = std::mem::zeroed::<libc::sched_param>();
86 p.sched_priority = priority;
87 p
88 };
89 let rc = unsafe { libc::pthread_setschedparam(thread, RT_POLICY, ¶m) };
90 if rc != 0 {
91 return Err(format!(
92 "pthread_setschedparam({}, prio {}) failed with errno {}",
93 name, priority, rc
94 ));
95 }
96
97 let mut actual_policy = 0_i32;
98 let mut actual_param = unsafe { std::mem::zeroed::<libc::sched_param>() };
99 let rc = unsafe {
100 libc::pthread_getschedparam(thread, &mut actual_policy, &mut actual_param)
101 };
102 if rc != 0 {
103 return Err(format!(
104 "pthread_getschedparam({}) failed with errno {}",
105 name, rc
106 ));
107 }
108 if actual_policy != RT_POLICY || actual_param.sched_priority != priority {
109 return Err(format!(
110 "realtime verification failed for {}: policy {}, prio {}",
111 name, actual_policy, actual_param.sched_priority
112 ));
113 }
114 Ok(())
115 }
116 #[cfg(not(unix))]
117 {
118 let _ = name;
119 let _ = priority;
120 Err("Realtime thread priority is not supported on this platform".to_string())
121 }
122 }
123
124 fn lock_memory_pages() -> Result<(), String> {
125 #[cfg(unix)]
126 {
127 let rc = unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) };
128 if rc == 0 {
129 Ok(())
130 } else {
131 Err(format!(
132 "mlockall(MCL_CURRENT|MCL_FUTURE) failed: {}",
133 std::io::Error::last_os_error()
134 ))
135 }
136 }
137 #[cfg(not(unix))]
138 {
139 Err("mlockall is not supported on this platform".to_string())
140 }
141 }
142
143 pub fn new(
144 driver: B::Driver,
145 midi_hub: B::MidiHub,
146 rx: Receiver<Message>,
147 tx: Sender<Message>,
148 ) -> Self {
149 let cycle_frames = driver.cycle_samples() as u32;
150 Self {
151 driver: Some(driver),
152 midi_hub,
153 rx,
154 tx,
155 cycle_frames,
156 pending_midi_out_events: vec![],
157 pending_midi_out_sorted: true,
158 midi_stop: Arc::new(AtomicBool::new(false)),
159 playing: false,
160 }
161 }
162
163 fn driver_mut(&mut self) -> &mut B::Driver {
164 self.driver
165 .as_mut()
166 .expect("driver is only absent while a cycle runs on the blocking thread")
167 }
168
169 fn run_cycle_blocking(mut driver: B::Driver) -> (B::Driver, Result<(), String>) {
174 if let Err(e) = Self::configure_rt_thread(B::WORKER_THREAD_NAME, RT_PRIORITY_WORKER) {
175 static WARNED: std::sync::atomic::AtomicBool =
176 std::sync::atomic::AtomicBool::new(false);
177 if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
178 tracing::warn!(
179 "{} cycle thread realtime priority not enabled: {}",
180 B::LABEL,
181 e
182 );
183 }
184 }
185 let result = driver.run_cycle_for_worker();
186 (driver, result)
187 }
188
189 pub async fn work(mut self) {
190 crate::enable_flush_denormals_to_zero();
191 if let Err(e) = Self::lock_memory_pages() {
192 error!("{} worker memory lock not enabled: {}", B::LABEL, e);
193 }
194 if let Err(e) = Self::configure_rt_thread(B::WORKER_THREAD_NAME, RT_PRIORITY_WORKER) {
195 error!("{} worker realtime priority not enabled: {}", B::LABEL, e);
196 }
197 #[cfg(target_os = "macos")]
198 unsafe {
199 libc::pthread_set_qos_class_self_np(libc::qos_class_t::QOS_CLASS_USER_INTERACTIVE, 0);
200 }
201
202 #[cfg(unix)]
203 {
204 let has_fds = self
205 .driver
206 .as_ref()
207 .is_some_and(|d| d.capture_fd().is_some() && d.playback_fd().is_some());
208 if has_fds {
209 self.work_async().await;
210 return;
211 }
212 }
213
214 self.work_legacy().await;
215 }
216
217 #[cfg(unix)]
218 async fn work_async(&mut self) {
219 let mut cycle_running = false;
220 let (cycle_tx, mut cycle_rx) =
221 tokio::sync::mpsc::channel::<(B::Driver, Result<(), String>)>(1);
222 let mut midi_input_poll = tokio::time::interval(MIDI_INPUT_POLL_INTERVAL);
223 loop {
224 tokio::select! {
225 msg = self.rx.recv(), if !cycle_running => {
230 let msg = match msg {
231 Some(m) => m,
232 None => {
233 self.driver_mut().request_stop();
234 self.shutdown_channel_closed();
235 return;
236 }
237 };
238 match msg {
239 Message::Request(crate::message::Action::Quit) => {
240 self.driver_mut().request_stop();
241 self.shutdown_quit();
242 return;
243 }
244 Message::TracksFinished => {
245 self.flush_pending_midi_out();
246 self.drain_midi_input().await;
247 if !cycle_running {
248 cycle_running = true;
249 let tx = cycle_tx.clone();
250 let driver = self.driver.take().expect(
251 "driver is only absent while a cycle is running",
252 );
253 tokio::task::spawn_blocking(move || {
254 let _ = tx.blocking_send(Self::run_cycle_blocking(driver));
255 });
256 }
257 }
258 Message::HWMidiOutEvents(mut events) => {
259 self.pending_midi_out_events.append(&mut events);
260 self.pending_midi_out_sorted = false;
261 if !self.playing {
265 self.flush_pending_midi_out();
266 }
267 }
268 Message::ClearHWMidiOutEvents => {
269 self.pending_midi_out_events.clear();
270 self.pending_midi_out_sorted = true;
271 }
272 Message::HWSetPlaying(playing) => {
273 self.playing = playing;
274 self.driver_mut().set_playing(playing);
275 }
276 Message::HWSetOutputGainBalance { gain, balance } => {
277 self.driver_mut().set_output_gain_balance(gain, balance);
278 }
279 Message::HWOpenMidiInputDevice(device) => {
280 let result = self.midi_hub.open_input(&device);
281 let action = crate::message::Action::OpenMidiInputDevice(device);
282 let _ = self.tx.send(Message::Response(result.map(|_| action))).await;
283 }
284 Message::HWOpenMidiOutputDevice(device) => {
285 let result = self.midi_hub.open_output(&device);
286 let action = crate::message::Action::OpenMidiOutputDevice(device);
287 let _ = self.tx.send(Message::Response(result.map(|_| action))).await;
288 }
289 Message::HWCloseMidiDevices => {
290 self.midi_hub.close_all();
291 }
292 _ => {}
293 }
294 }
295 result = cycle_rx.recv(), if cycle_running => {
296 cycle_running = false;
297 if let Some((driver, result)) = result {
298 self.driver = Some(driver);
299 if let Err(e) = result {
300 error!("{} cycle error: {}", B::LABEL, e);
301 let _ = self.tx.send(Message::Response(Err(format!(
302 "{} cycle error: {}", B::LABEL, e
303 )))).await;
304 }
305 }
306 if let Err(e) = self.tx.send(Message::HWFinished).await {
307 error!("{} worker failed to send HWFinished: {}", B::LABEL, e);
308 }
309 }
310 _ = midi_input_poll.tick(), if !cycle_running => {
315 self.drain_midi_input().await;
316 }
317 }
318 }
319 }
320
321 async fn work_legacy(&mut self) {
322 let mut midi_input_poll = tokio::time::interval(MIDI_INPUT_POLL_INTERVAL);
323 loop {
324 let msg = tokio::select! {
325 msg = self.rx.recv() => match msg {
326 Some(msg) => msg,
327 None => {
328 self.driver_mut().request_stop();
329 self.shutdown_midi();
330 self.driver_mut().close_fds();
331 return;
332 }
333 },
334 _ = midi_input_poll.tick() => {
337 self.drain_midi_input().await;
338 continue;
339 }
340 };
341 match msg {
342 Message::Request(crate::message::Action::Quit) => {
343 self.driver_mut().request_stop();
344 self.flush_pending_midi_out();
345 self.shutdown_midi();
346 self.driver_mut().close_fds();
347 self.driver_mut().request_stop();
348 return;
349 }
350 Message::TracksFinished => {
351 self.flush_pending_midi_out();
352 self.drain_midi_input().await;
353 let driver = self
357 .driver
358 .take()
359 .expect("driver is only absent while a cycle is running");
360 let cycle =
361 tokio::task::spawn_blocking(move || Self::run_cycle_blocking(driver));
362 match cycle.await {
363 Ok((driver, result)) => {
364 self.driver = Some(driver);
365 if let Err(e) = result {
366 error!("{} assist cycle error: {}", B::LABEL, e);
367 let _ = self
368 .tx
369 .send(Message::Response(Err(format!(
370 "{} assist cycle error: {}",
371 B::LABEL,
372 e
373 ))))
374 .await;
375 }
376 }
377 Err(e) => {
378 error!("{} cycle task failed: {}", B::LABEL, e);
379 return;
380 }
381 }
382 if let Err(e) = self.tx.send(Message::HWFinished).await {
383 error!(
384 "{} worker failed to send HWFinished to engine: {}",
385 B::LABEL,
386 e
387 );
388 }
389 }
390 Message::HWMidiOutEvents(mut events) => {
391 self.pending_midi_out_events.append(&mut events);
392 self.pending_midi_out_sorted = false;
393 if !self.playing {
396 self.flush_pending_midi_out();
397 }
398 }
399 Message::ClearHWMidiOutEvents => {
400 self.pending_midi_out_events.clear();
401 self.pending_midi_out_sorted = true;
402 }
403 Message::HWSetPlaying(playing) => {
404 self.playing = playing;
405 self.driver_mut().set_playing(playing);
406 }
407 Message::HWSetOutputGainBalance { gain, balance } => {
408 self.driver_mut().set_output_gain_balance(gain, balance);
409 }
410 Message::HWOpenMidiInputDevice(device) => {
411 let result = self.midi_hub.open_input(&device);
412 let action = crate::message::Action::OpenMidiInputDevice(device);
413 let _ = self
414 .tx
415 .send(Message::Response(result.map(|_| action)))
416 .await;
417 }
418 Message::HWOpenMidiOutputDevice(device) => {
419 let result = self.midi_hub.open_output(&device);
420 let action = crate::message::Action::OpenMidiOutputDevice(device);
421 let _ = self
422 .tx
423 .send(Message::Response(result.map(|_| action)))
424 .await;
425 }
426 Message::HWCloseMidiDevices => {
427 self.midi_hub.close_all();
428 }
429 _ => {}
430 }
431 }
432 }
433
434 fn flush_pending_midi_out(&mut self) {
435 if self.pending_midi_out_events.is_empty() {
436 return;
437 }
438 if !self.pending_midi_out_sorted {
439 self.pending_midi_out_events.sort_by(|a, b| {
440 a.event
441 .frame
442 .cmp(&b.event.frame)
443 .then_with(|| a.device.cmp(&b.device))
444 });
445 self.pending_midi_out_sorted = true;
446 }
447 self.midi_hub.write_events(&self.pending_midi_out_events);
448 self.pending_midi_out_events.clear();
449 }
450
451 async fn drain_midi_input(&mut self) {
452 let mut midi_in_events = Vec::with_capacity(64);
453 self.midi_hub.read_events_into(&mut midi_in_events);
454 if midi_in_events.is_empty() {
455 return;
456 }
457 spread_hw_event_frames(&mut midi_in_events, self.cycle_frames);
458 let _ = self.tx.send(Message::HWMidiEvents(midi_in_events)).await;
459 }
460
461 fn shutdown_midi(&mut self) {
462 self.midi_stop.store(true, Ordering::Release);
463 self.midi_hub.wake_input_waiter();
464 self.midi_hub.close_all();
465 }
466
467 #[cfg(unix)]
468 fn shutdown_quit(&mut self) {
469 self.driver_mut().request_stop();
470 self.flush_pending_midi_out();
471 self.shutdown_midi();
472 self.driver_mut().close_fds();
473 self.driver_mut().request_stop();
474 }
475
476 #[cfg(unix)]
477 fn shutdown_channel_closed(&mut self) {
478 self.driver_mut().request_stop();
479 self.shutdown_midi();
480 self.driver_mut().close_fds();
481 self.driver_mut().request_stop();
482 }
483}
484
485fn spread_hw_event_frames(events: &mut [HwMidiEvent], frames: u32) {
486 if events.len() <= 1 || frames <= 1 {
487 return;
488 }
489 let n = events.len() as u32;
490 for (idx, event) in events.iter_mut().enumerate() {
491 let pos = idx as u32;
492 event.event.frame = ((pos as u64 * (frames - 1) as u64) / n as u64) as u32;
493 }
494}