Feat: tweak and fix from last night workshop
Some checks failed
Deploy Website / deploy (push) Failing after 4m46s
Some checks failed
Deploy Website / deploy (push) Failing after 4m46s
This commit is contained in:
@@ -1,45 +1,52 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
#[cfg(target_os = "linux")]
|
||||
mod memory {
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
static MLOCKALL_CALLED: AtomicBool = AtomicBool::new(false);
|
||||
static MLOCKALL_SUCCESS: AtomicBool = AtomicBool::new(false);
|
||||
static MLOCKALL_CALLED: AtomicBool = AtomicBool::new(false);
|
||||
static MLOCKALL_SUCCESS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Locks all current and future memory pages to prevent page faults during RT execution.
|
||||
/// Must be called BEFORE spawning any threads for maximum effectiveness.
|
||||
/// Returns true if mlockall succeeded, false otherwise (which is common without rtprio).
|
||||
#[cfg(unix)]
|
||||
pub fn lock_memory() -> bool {
|
||||
if MLOCKALL_CALLED.swap(true, Ordering::SeqCst) {
|
||||
return MLOCKALL_SUCCESS.load(Ordering::SeqCst);
|
||||
/// Locks all current and future memory pages to prevent page faults during RT execution.
|
||||
/// Must be called BEFORE spawning any threads for maximum effectiveness.
|
||||
pub fn lock_memory() -> bool {
|
||||
if MLOCKALL_CALLED.swap(true, Ordering::SeqCst) {
|
||||
return MLOCKALL_SUCCESS.load(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let result = unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) };
|
||||
|
||||
if result == 0 {
|
||||
MLOCKALL_SUCCESS.store(true, Ordering::SeqCst);
|
||||
true
|
||||
} else {
|
||||
let errno = std::io::Error::last_os_error();
|
||||
eprintln!("[cagire] mlockall failed: {errno}");
|
||||
eprintln!("[cagire] Memory locking disabled. For best RT performance on Linux:");
|
||||
eprintln!("[cagire] 1. Add user to 'audio' group: sudo usermod -aG audio $USER");
|
||||
eprintln!("[cagire] 2. Add to /etc/security/limits.conf:");
|
||||
eprintln!("[cagire] @audio - memlock unlimited");
|
||||
eprintln!("[cagire] 3. Log out and back in");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let result = unsafe { libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) };
|
||||
|
||||
if result == 0 {
|
||||
MLOCKALL_SUCCESS.store(true, Ordering::SeqCst);
|
||||
true
|
||||
} else {
|
||||
// Get the actual error for better diagnostics
|
||||
let errno = std::io::Error::last_os_error();
|
||||
eprintln!("[cagire] mlockall failed: {errno}");
|
||||
eprintln!("[cagire] Memory locking disabled. For best RT performance on Linux:");
|
||||
eprintln!("[cagire] 1. Add user to 'audio' group: sudo usermod -aG audio $USER");
|
||||
eprintln!("[cagire] 2. Add to /etc/security/limits.conf:");
|
||||
eprintln!("[cagire] @audio - memlock unlimited");
|
||||
eprintln!("[cagire] 3. Log out and back in");
|
||||
false
|
||||
#[allow(dead_code)]
|
||||
pub fn is_memory_locked() -> bool {
|
||||
MLOCKALL_SUCCESS.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use memory::{is_memory_locked, lock_memory};
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn lock_memory() -> bool {
|
||||
// Windows: VirtualLock exists but isn't typically needed for audio
|
||||
true
|
||||
}
|
||||
|
||||
/// Check if memory locking is active.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[allow(dead_code)]
|
||||
pub fn is_memory_locked() -> bool {
|
||||
MLOCKALL_SUCCESS.load(Ordering::Relaxed)
|
||||
false
|
||||
}
|
||||
|
||||
/// Attempts to set realtime scheduling priority for the current thread.
|
||||
|
||||
@@ -531,6 +531,7 @@ impl KeyCache {
|
||||
pub(crate) struct SequencerState {
|
||||
audio_state: AudioState,
|
||||
pattern_cache: PatternCache,
|
||||
pending_updates: HashMap<(usize, usize), PatternSnapshot>,
|
||||
runs_counter: RunsCounter,
|
||||
step_traces: Arc<StepTracesMap>,
|
||||
event_count: usize,
|
||||
@@ -559,6 +560,7 @@ impl SequencerState {
|
||||
Self {
|
||||
audio_state: AudioState::new(),
|
||||
pattern_cache: PatternCache::new(),
|
||||
pending_updates: HashMap::new(),
|
||||
runs_counter: RunsCounter::new(),
|
||||
step_traces: Arc::new(HashMap::new()),
|
||||
event_count: 0,
|
||||
@@ -596,7 +598,14 @@ impl SequencerState {
|
||||
pattern,
|
||||
data,
|
||||
} => {
|
||||
self.pattern_cache.set(bank, pattern, data);
|
||||
let id = PatternId { bank, pattern };
|
||||
let is_active = self.audio_state.active_patterns.contains_key(&id);
|
||||
let has_cache = self.pattern_cache.get(bank, pattern).is_some();
|
||||
if is_active && has_cache {
|
||||
self.pending_updates.insert((bank, pattern), data);
|
||||
} else {
|
||||
self.pattern_cache.set(bank, pattern, data);
|
||||
}
|
||||
}
|
||||
SeqCommand::PatternStart {
|
||||
bank,
|
||||
@@ -652,6 +661,10 @@ impl SequencerState {
|
||||
}
|
||||
}
|
||||
SeqCommand::StopAll => {
|
||||
// Flush pending updates so cache stays current for future launches
|
||||
for ((bank, pattern), snapshot) in self.pending_updates.drain() {
|
||||
self.pattern_cache.set(bank, pattern, snapshot);
|
||||
}
|
||||
self.audio_state.active_patterns.clear();
|
||||
self.audio_state.pending_starts.clear();
|
||||
self.audio_state.pending_stops.clear();
|
||||
@@ -715,6 +728,10 @@ impl SequencerState {
|
||||
Arc::make_mut(&mut self.step_traces).retain(|&(bank, pattern, _), _| {
|
||||
bank != pending.id.bank || pattern != pending.id.pattern
|
||||
});
|
||||
let key = (pending.id.bank, pending.id.pattern);
|
||||
if let Some(snapshot) = self.pending_updates.remove(&key) {
|
||||
self.pattern_cache.set(key.0, key.1, snapshot);
|
||||
}
|
||||
}
|
||||
self.audio_state.pending_starts.clear();
|
||||
self.audio_state.prev_beat = -1.0;
|
||||
@@ -773,6 +790,11 @@ impl SequencerState {
|
||||
Arc::make_mut(&mut self.step_traces).retain(|&(bank, pattern, _), _| {
|
||||
bank != pending.id.bank || pattern != pending.id.pattern
|
||||
});
|
||||
// Flush pending update so cache stays current for future launches
|
||||
let key = (pending.id.bank, pending.id.pattern);
|
||||
if let Some(snapshot) = self.pending_updates.remove(&key) {
|
||||
self.pattern_cache.set(key.0, key.1, snapshot);
|
||||
}
|
||||
self.buf_stopped.push(pending.id);
|
||||
}
|
||||
}
|
||||
@@ -921,6 +943,14 @@ impl SequencerState {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply deferred updates for patterns that just completed an iteration
|
||||
for completed_id in &self.buf_completed_iterations {
|
||||
let key = (completed_id.bank, completed_id.pattern);
|
||||
if let Some(snapshot) = self.pending_updates.remove(&key) {
|
||||
self.pattern_cache.set(key.0, key.1, snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
@@ -1872,8 +1902,9 @@ mod tests {
|
||||
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, fires 1 step. step_index=0%2=0 fires, advances to 1
|
||||
// Update pattern to length 2 while running — deferred until iteration boundary
|
||||
// beat=1.25: update is deferred (pattern active), still length 4
|
||||
// step_index=0 fires, advances to 1
|
||||
state.tick(tick_with(
|
||||
vec![SeqCommand::PatternUpdate {
|
||||
bank: 0,
|
||||
@@ -1883,10 +1914,21 @@ mod tests {
|
||||
1.25,
|
||||
));
|
||||
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
|
||||
assert_eq!(ap.step_index, 1); // still length 4
|
||||
|
||||
// Advance through remaining steps of original length-4 pattern
|
||||
state.tick(tick_at(1.5, true)); // step 1→2
|
||||
state.tick(tick_at(1.75, true)); // step 2→3
|
||||
state.tick(tick_at(2.0, true)); // step 3→wraps to 0, iteration completes, update applies
|
||||
|
||||
// Now length=2 is applied. Next tick uses new length.
|
||||
// beat=2.25: step 0 fires, advances to 1
|
||||
state.tick(tick_at(2.25, true));
|
||||
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
|
||||
assert_eq!(ap.step_index, 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));
|
||||
// beat=2.5: step 1 fires, wraps to 0 (length 2)
|
||||
state.tick(tick_at(2.5, true));
|
||||
let ap = state.audio_state.active_patterns.get(&pid(0, 0)).unwrap();
|
||||
assert_eq!(ap.step_index, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user