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(unix)]
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(target_os = "windows")]
117 {
118 use std::{cell::Cell, ffi::OsStr, os::windows::ffi::OsStrExt};
119
120 #[link(name = "avrt")]
121 unsafe extern "system" {
122 fn AvSetMmThreadCharacteristicsW(
123 task_name: *const u16,
124 task_index: *mut u32,
125 ) -> isize;
126 }
127
128 let _ = priority;
129 thread_local! {
130 static MMCSS_TASK_HANDLE: Cell<isize> = const { Cell::new(0) };
131 }
132
133 MMCSS_TASK_HANDLE.with(|handle| {
134 if handle.get() != 0 {
135 return Ok(());
136 }
137
138 let task_name: Vec<u16> = OsStr::new("Pro Audio")
139 .encode_wide()
140 .chain(Some(0))
141 .collect();
142 let mut task_index = 0_u32;
143 let mmcss_handle =
144 unsafe { AvSetMmThreadCharacteristicsW(task_name.as_ptr(), &mut task_index) };
145 if mmcss_handle == 0 {
146 Err(format!(
147 "AvSetMmThreadCharacteristicsW({name}, Pro Audio) failed: {}",
148 std::io::Error::last_os_error()
149 ))
150 } else {
151 handle.set(mmcss_handle);
152 Ok(())
153 }
154 })
155 }
156 #[cfg(all(not(unix), not(target_os = "windows")))]
157 {
158 let _ = name;
159 let _ = priority;
160 Err("Realtime thread priority is not supported on this platform".to_string())
161 }
162 }
163
164 #[cfg(unix)]
165 fn lock_memory_pages() -> Result<(), String> {
166 let rc = unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) };
167 if rc == 0 {
168 Ok(())
169 } else {
170 Err(format!(
171 "mlockall(MCL_CURRENT|MCL_FUTURE) failed: {}",
172 std::io::Error::last_os_error()
173 ))
174 }
175 }
176
177 pub fn new(
178 driver: B::Driver,
179 midi_hub: B::MidiHub,
180 rx: Receiver<Message>,
181 tx: Sender<Message>,
182 ) -> Self {
183 let cycle_frames = driver.cycle_samples() as u32;
184 Self {
185 driver: Some(driver),
186 midi_hub,
187 rx,
188 tx,
189 cycle_frames,
190 pending_midi_out_events: vec![],
191 pending_midi_out_sorted: true,
192 midi_stop: Arc::new(AtomicBool::new(false)),
193 playing: false,
194 }
195 }
196
197 fn driver_mut(&mut self) -> &mut B::Driver {
198 self.driver
199 .as_mut()
200 .expect("driver is only absent while a cycle runs on the blocking thread")
201 }
202
203 fn run_cycle_blocking(mut driver: B::Driver) -> (B::Driver, Result<(), String>) {
208 let rt_start = std::time::Instant::now();
209 if let Err(e) = Self::configure_rt_thread(B::WORKER_THREAD_NAME, RT_PRIORITY_WORKER) {
210 static WARNED: std::sync::atomic::AtomicBool =
211 std::sync::atomic::AtomicBool::new(false);
212 if !WARNED.swap(true, std::sync::atomic::Ordering::Relaxed) {
213 tracing::warn!(
214 "{} cycle thread realtime priority not enabled: {}",
215 B::LABEL,
216 e
217 );
218 }
219 }
220 let _rt_us = rt_start.elapsed().as_micros() as u64;
221 let _cycle_start = std::time::Instant::now();
222 let result = driver.run_cycle_for_worker();
223 let _cycle_us = _cycle_start.elapsed().as_micros() as u64;
224 (driver, result)
225 }
226
227 pub async fn work(mut self) {
228 crate::enable_flush_denormals_to_zero();
229 #[cfg(unix)]
230 {
231 if let Err(e) = Self::lock_memory_pages() {
232 error!("{} worker memory lock not enabled: {}", B::LABEL, e);
233 }
234 }
235 if let Err(e) = Self::configure_rt_thread(B::WORKER_THREAD_NAME, RT_PRIORITY_WORKER) {
236 error!("{} worker realtime priority not enabled: {}", B::LABEL, e);
237 }
238 #[cfg(unix)]
239 {
240 let has_fds = self
241 .driver
242 .as_ref()
243 .is_some_and(|d| d.capture_fd().is_some() && d.playback_fd().is_some());
244 if has_fds {
245 self.work_async().await;
246 return;
247 }
248 }
249
250 self.work_legacy().await;
251 }
252
253 #[cfg(unix)]
254 async fn work_async(&mut self) {
255 let mut cycle_running = false;
256 let (cycle_tx, mut cycle_rx) =
257 tokio::sync::mpsc::channel::<(B::Driver, Result<(), String>)>(1);
258 let mut midi_input_poll = tokio::time::interval(MIDI_INPUT_POLL_INTERVAL);
259 loop {
260 tokio::select! {
261 msg = self.rx.recv(), if !cycle_running => {
266 let msg = match msg {
267 Some(m) => m,
268 None => {
269 self.driver_mut().request_stop();
270 self.shutdown_channel_closed();
271 return;
272 }
273 };
274 match msg {
275 Message::Request(crate::message::Action::Quit) => {
276 self.driver_mut().request_stop();
277 self.shutdown_quit();
278 return;
279 }
280 Message::TracksFinished => {
281 self.flush_pending_midi_out();
282 self.drain_midi_input().await;
283 if !cycle_running {
284 cycle_running = true;
285 let tx = cycle_tx.clone();
286 let driver = self.driver.take().expect(
287 "driver is only absent while a cycle is running",
288 );
289 tokio::task::spawn_blocking(move || {
290 let _ = tx.blocking_send(Self::run_cycle_blocking(driver));
291 });
292 }
293 }
294 Message::HWMidiOutEvents(mut events) => {
295 self.pending_midi_out_events.append(&mut events);
296 self.pending_midi_out_sorted = false;
297 if !self.playing {
301 self.flush_pending_midi_out();
302 }
303 }
304 Message::ClearHWMidiOutEvents => {
305 self.pending_midi_out_events.clear();
306 self.pending_midi_out_sorted = true;
307 }
308 Message::HWSetPlaying(playing) => {
309 self.playing = playing;
310 self.driver_mut().set_playing(playing);
311 }
312 Message::HWSetOutputGainBalance { gain, balance } => {
313 self.driver_mut().set_output_gain_balance(gain, balance);
314 }
315 Message::HWOpenMidiInputDevice(device) => {
316 let result = self.midi_hub.open_input(&device);
317 let action = crate::message::Action::OpenMidiInputDevice(device);
318 let _ = self.tx.send(Message::Response(result.map(|_| action))).await;
319 }
320 Message::HWOpenMidiOutputDevice(device) => {
321 let result = self.midi_hub.open_output(&device);
322 let action = crate::message::Action::OpenMidiOutputDevice(device);
323 let _ = self.tx.send(Message::Response(result.map(|_| action))).await;
324 }
325 Message::HWCloseMidiDevices => {
326 self.midi_hub.close_all();
327 }
328 _ => {}
329 }
330 }
331 result = cycle_rx.recv(), if cycle_running => {
332 cycle_running = false;
333 if let Some((driver, result)) = result {
334 self.driver = Some(driver);
335 if let Err(e) = result {
336 error!("{} cycle error: {}", B::LABEL, e);
337 let _ = self.tx.send(Message::Response(Err(format!(
338 "{} cycle error: {}", B::LABEL, e
339 )))).await;
340 }
341 }
342 if let Err(e) = self.tx.send(Message::HWFinished).await {
343 error!("{} worker failed to send HWFinished: {}", B::LABEL, e);
344 }
345 }
346 _ = midi_input_poll.tick(), if !cycle_running => {
351 self.drain_midi_input().await;
352 }
353 }
354 }
355 }
356
357 async fn work_legacy(&mut self) {
358 let mut midi_input_poll = tokio::time::interval(MIDI_INPUT_POLL_INTERVAL);
359 loop {
360 let msg = tokio::select! {
361 msg = self.rx.recv() => match msg {
362 Some(msg) => msg,
363 None => {
364 self.driver_mut().request_stop();
365 self.shutdown_midi();
366 self.driver_mut().close_fds();
367 return;
368 }
369 },
370 _ = midi_input_poll.tick() => {
373 self.drain_midi_input().await;
374 continue;
375 }
376 };
377 match msg {
378 Message::Request(crate::message::Action::Quit) => {
379 self.driver_mut().request_stop();
380 self.flush_pending_midi_out();
381 self.shutdown_midi();
382 self.driver_mut().close_fds();
383 self.driver_mut().request_stop();
384 return;
385 }
386 Message::TracksFinished => {
387 self.flush_pending_midi_out();
388 self.drain_midi_input().await;
389 let driver = self
393 .driver
394 .take()
395 .expect("driver is only absent while a cycle is running");
396 let cycle =
397 tokio::task::spawn_blocking(move || Self::run_cycle_blocking(driver));
398 match cycle.await {
399 Ok((driver, result)) => {
400 self.driver = Some(driver);
401 if let Err(e) = result {
402 error!("{} assist cycle error: {}", B::LABEL, e);
403 let _ = self
404 .tx
405 .send(Message::Response(Err(format!(
406 "{} assist cycle error: {}",
407 B::LABEL,
408 e
409 ))))
410 .await;
411 }
412 }
413 Err(e) => {
414 error!("{} cycle task failed: {}", B::LABEL, e);
415 return;
416 }
417 }
418 if let Err(e) = self.tx.send(Message::HWFinished).await {
419 error!(
420 "{} worker failed to send HWFinished to engine: {}",
421 B::LABEL,
422 e
423 );
424 }
425 }
426 Message::HWMidiOutEvents(mut events) => {
427 self.pending_midi_out_events.append(&mut events);
428 self.pending_midi_out_sorted = false;
429 if !self.playing {
432 self.flush_pending_midi_out();
433 }
434 }
435 Message::ClearHWMidiOutEvents => {
436 self.pending_midi_out_events.clear();
437 self.pending_midi_out_sorted = true;
438 }
439 Message::HWSetPlaying(playing) => {
440 self.playing = playing;
441 self.driver_mut().set_playing(playing);
442 }
443 Message::HWSetOutputGainBalance { gain, balance } => {
444 self.driver_mut().set_output_gain_balance(gain, balance);
445 }
446 Message::HWOpenMidiInputDevice(device) => {
447 let result = self.midi_hub.open_input(&device);
448 let action = crate::message::Action::OpenMidiInputDevice(device);
449 let _ = self
450 .tx
451 .send(Message::Response(result.map(|_| action)))
452 .await;
453 }
454 Message::HWOpenMidiOutputDevice(device) => {
455 let result = self.midi_hub.open_output(&device);
456 let action = crate::message::Action::OpenMidiOutputDevice(device);
457 let _ = self
458 .tx
459 .send(Message::Response(result.map(|_| action)))
460 .await;
461 }
462 Message::HWCloseMidiDevices => {
463 self.midi_hub.close_all();
464 }
465 _ => {}
466 }
467 }
468 }
469
470 fn flush_pending_midi_out(&mut self) {
471 if self.pending_midi_out_events.is_empty() {
472 return;
473 }
474 if !self.pending_midi_out_sorted {
475 self.pending_midi_out_events.sort_by(|a, b| {
476 a.event
477 .frame
478 .cmp(&b.event.frame)
479 .then_with(|| a.device.cmp(&b.device))
480 });
481 self.pending_midi_out_sorted = true;
482 }
483 self.midi_hub.write_events(&self.pending_midi_out_events);
484 self.pending_midi_out_events.clear();
485 }
486
487 async fn drain_midi_input(&mut self) {
488 let mut midi_in_events = Vec::with_capacity(64);
489 self.midi_hub.read_events_into(&mut midi_in_events);
490 if midi_in_events.is_empty() {
491 return;
492 }
493 spread_hw_event_frames(&mut midi_in_events, self.cycle_frames);
494 let _ = self.tx.send(Message::HWMidiEvents(midi_in_events)).await;
495 }
496
497 fn shutdown_midi(&mut self) {
498 self.midi_stop.store(true, Ordering::Release);
499 self.midi_hub.wake_input_waiter();
500 self.midi_hub.close_all();
501 }
502
503 #[cfg(unix)]
504 fn shutdown_quit(&mut self) {
505 self.driver_mut().request_stop();
506 self.flush_pending_midi_out();
507 self.shutdown_midi();
508 self.driver_mut().close_fds();
509 self.driver_mut().request_stop();
510 }
511
512 #[cfg(unix)]
513 fn shutdown_channel_closed(&mut self) {
514 self.driver_mut().request_stop();
515 self.shutdown_midi();
516 self.driver_mut().close_fds();
517 self.driver_mut().request_stop();
518 }
519}
520
521fn spread_hw_event_frames(events: &mut [HwMidiEvent], frames: u32) {
522 if events.len() <= 1 || frames <= 1 {
523 return;
524 }
525 let n = events.len() as u32;
526 for (idx, event) in events.iter_mut().enumerate() {
527 let pos = idx as u32;
528 event.event.frame = ((pos as u64 * (frames - 1) as u64) / n as u64) as u32;
529 }
530}