Insane linux fixes
Some checks failed
Deploy Website / deploy (push) Failing after 4m45s

This commit is contained in:
2026-02-03 01:15:07 +01:00
parent af81c94207
commit cd223592a7
2 changed files with 110 additions and 22 deletions

View File

@@ -843,7 +843,14 @@ impl SequencerState {
let beat_int = (beat * 4.0 * speed_mult).floor() as i64;
let prev_beat_int = (prev_beat * 4.0 * speed_mult).floor() as i64;
if beat_int != prev_beat_int && prev_beat >= 0.0 {
// Fire ALL skipped steps when scheduler jitter causes us to miss beats
let steps_to_fire = if prev_beat >= 0.0 {
(beat_int - prev_beat_int).clamp(0, 16) as usize
} else {
0
};
for _ in 0..steps_to_fire {
result.any_step_fired = true;
let step_idx = active.step_index % pattern.length;
@@ -1054,15 +1061,30 @@ fn sequencer_loop(
#[cfg(unix)]
{
use thread_priority::unix::{
set_thread_priority_and_policy, thread_native_id, RealtimeThreadSchedulePolicy,
ThreadSchedulePolicy,
set_thread_priority_and_policy, thread_native_id, NormalThreadSchedulePolicy,
RealtimeThreadSchedulePolicy, ThreadSchedulePolicy,
};
let policy = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo);
if let Err(e) =
set_thread_priority_and_policy(thread_native_id(), ThreadPriority::Max, policy)
{
eprintln!("Warning: Could not set SCHED_FIFO: {e:?}");
let tid = thread_native_id();
// Try SCHED_FIFO first (requires CAP_SYS_NICE on Linux)
let fifo = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::Fifo);
if set_thread_priority_and_policy(tid, ThreadPriority::Max, fifo).is_err() {
// Try SCHED_RR (round-robin realtime, sometimes works without caps)
let rr = ThreadSchedulePolicy::Realtime(RealtimeThreadSchedulePolicy::RoundRobin);
if set_thread_priority_and_policy(tid, ThreadPriority::Max, rr).is_err() {
// Fall back to highest normal priority (SCHED_OTHER)
let _ = set_thread_priority_and_policy(
tid,
ThreadPriority::Max,
ThreadSchedulePolicy::Normal(NormalThreadSchedulePolicy::Other),
);
// Also try nice -20 via libc on Linux
#[cfg(target_os = "linux")]
unsafe {
libc::setpriority(libc::PRIO_PROCESS, 0, -20);
}
}
}
}
@@ -1215,10 +1237,54 @@ fn sequencer_loop(
let substep_us = (secs_per_substep * 1_000_000.0) as u64;
// Sleep for most of the substep duration, clamped to reasonable bounds
let sleep_us = substep_us.saturating_sub(500).clamp(50, 2000);
thread::sleep(Duration::from_micros(sleep_us));
precise_sleep(sleep_us);
}
}
/// High-precision sleep using timerfd on Linux, falling back to thread::sleep elsewhere
#[cfg(target_os = "linux")]
fn precise_sleep(micros: u64) {
use nix::sys::timerfd::{ClockId, Expiration, TimerFd, TimerFlags, TimerSetTimeFlags};
use std::os::fd::AsRawFd;
thread_local! {
static TIMER: std::cell::RefCell<Option<TimerFd>> = const { std::cell::RefCell::new(None) };
}
TIMER.with(|timer_cell| {
let mut timer_ref = timer_cell.borrow_mut();
let timer = timer_ref.get_or_insert_with(|| {
TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
.expect("Failed to create timerfd")
});
let duration = Duration::from_micros(micros);
if timer
.set(Expiration::OneShot(duration), TimerSetTimeFlags::empty())
.is_ok()
{
// Use poll to wait for the timer instead of read to avoid allocating
let mut pollfd = libc::pollfd {
fd: timer.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
unsafe {
libc::poll(&mut pollfd, 1, -1);
}
// Clear the timer by reading (discard the count)
let _ = timer.wait();
} else {
thread::sleep(duration);
}
});
}
#[cfg(not(target_os = "linux"))]
fn precise_sleep(micros: u64) {
thread::sleep(Duration::from_micros(micros));
}
fn parse_midi_command(cmd: &str) -> Option<(MidiCommand, Option<f64>)> {
if !cmd.starts_with("/midi/") {
return None;
@@ -1632,19 +1698,23 @@ mod tests {
0.5,
));
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 1);
assert_eq!(ap.iter, 0);
state.tick(tick_at(0.75, true));
// beat_int at 0.5 is 2, prev_beat_int at 0.0 is 0
// steps_to_fire = 2-0 = 2, firing steps 0 and 1, wrapping to 0
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 0);
assert_eq!(ap.iter, 1);
state.tick(tick_at(1.0, true));
// beat_int at 0.75 is 3, prev is 2, fires 1 step (step 0), advances to 1
state.tick(tick_at(0.75, true));
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 1);
assert_eq!(ap.iter, 1);
// beat_int at 1.0 is 4, prev is 3, fires 1 step (step 1), wraps to 0
state.tick(tick_at(1.0, true));
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 0);
assert_eq!(ap.iter, 2);
}
#[test]
@@ -1673,9 +1743,15 @@ mod tests {
0.5,
));
// At 2x speed: beat_int at 0.5 is (0.5*4*2)=4, prev at 0.0 is 0
// Fires 4 steps (0,1,2,3), advancing to step 4
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 4);
// beat_int at 0.625 is (0.625*4*2)=5, prev is 4, fires 1 step
state.tick(tick_at(0.625, true));
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 2);
assert_eq!(ap.step_index, 5);
}
#[test]
@@ -1840,14 +1916,22 @@ mod tests {
0.5,
));
// Advance to step_index=3
// beat_int at 0.5 is 2, prev at 0.0 is 0, fires 2 steps (0,1), step_index=2
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 2);
// beat_int at 0.75 is 3, prev is 2, fires 1 step (2), step_index=3
state.tick(tick_at(0.75, true));
state.tick(tick_at(1.0, true));
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 3);
// beat_int at 1.0 is 4, prev is 3, fires 1 step (3), wraps to step_index=0
state.tick(tick_at(1.0, true));
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 0);
// Update pattern to length 2 while running — step_index wraps via modulo
// beat=1.25: beat_int=5, prev=4, step fires. step_index=3%2=1 fires, advances to (3+1)%2=0
// beat=1.25: beat_int=5, prev=4, fires 1 step. step_index=0%2=0 fires, advances to 1
state.tick(tick_with(
vec![SeqCommand::PatternUpdate {
bank: 0,
@@ -1857,12 +1941,12 @@ mod tests {
1.25,
));
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 0);
assert_eq!(ap.step_index, 1);
// beat=1.5: beat_int=6, prev=5, step fires. step_index=0 fires, advances to 1
// beat=1.5: beat_int=6, prev=5, step fires. step_index=1 fires, wraps to 0
state.tick(tick_at(1.5, true));
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
assert_eq!(ap.step_index, 1);
assert_eq!(ap.step_index, 0);
}
#[test]