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