Skip to main content

max / audiofiles

Perf: allocation-free name sort, cap MIDI note buffer at the producer sort_contents compared str::to_lowercase() allocations (~1.7M allocs per sort at 100k rows); compare lowercased char iterators instead. The MIDI note-on buffer was capped only on GUI drain, so a stalled GUI under a dense stream could grow it unbounded; cap at the producer too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-22 00:25 UTC
Commit: 9e8e5a2e7c10d6aa67cbc2bdb7d85aed7315d69c
Parent: b3f7a24
2 files changed, +19 insertions, -2 deletions
@@ -68,12 +68,20 @@ pub fn connect(port_index: usize, shared: Arc<SharedState>) -> Result<MidiConnec
68 68 0x90 if velocity > 0 => {
69 69 // Note On
70 70 shared_cb.instrument.lock().note_on(note, velocity);
71 - shared_cb.midi_recent_notes.lock().push(MidiNoteEvent {
71 + let mut recent = shared_cb.midi_recent_notes.lock();
72 + recent.push(MidiNoteEvent {
72 73 note,
73 74 velocity,
74 75 note_name: note_name(note),
75 76 timestamp: Instant::now(),
76 77 });
78 + // Cap at the producer so a stalled GUI (minimized/occluded,
79 + // not draining) can't grow this unbounded under a dense MIDI
80 + // stream. The GUI keeps only the last 8 on drain.
81 + let len = recent.len();
82 + if len > 64 {
83 + recent.drain(..len - 64);
84 + }
77 85 }
78 86 0x80 | 0x90 => {
79 87 // Note Off (0x80 or 0x90 with velocity 0)
@@ -2,6 +2,15 @@ use tracing::{error, warn};
2 2
3 3 use super::*;
4 4
5 + /// Case-insensitive string comparison without allocating. `str::to_lowercase`
6 + /// allocates a `String` per call, which at 100k rows means ~1.7M allocations per
7 + /// sort; comparing lowercased char iterators is allocation-free.
8 + fn icase_cmp(a: &str, b: &str) -> std::cmp::Ordering {
9 + a.chars()
10 + .flat_map(char::to_lowercase)
11 + .cmp(b.chars().flat_map(char::to_lowercase))
12 + }
13 +
5 14 impl BrowserState {
6 15 /// Database ID of the currently active VFS, or `None` if the list is empty.
7 16 pub fn current_vfs_id(&self) -> Option<VfsId> {
@@ -104,7 +113,7 @@ impl BrowserState {
104 113 }
105 114
106 115 let cmp = match self.sort_column {
107 - SortColumn::Name => a.node.name.to_lowercase().cmp(&b.node.name.to_lowercase()),
116 + SortColumn::Name => icase_cmp(&a.node.name, &b.node.name),
108 117 SortColumn::Bpm => a.bpm.partial_cmp(&b.bpm).unwrap_or(std::cmp::Ordering::Equal),
109 118 SortColumn::Key => a.musical_key.cmp(&b.musical_key),
110 119 SortColumn::Duration => a