Squashed from work: - score: a headless music engraving, playback and notation engine - score: the notation app — pianist mode, editing, playback - piano_model: it was a plucked string by construction, and 20 voicings - score: one document you can pan, zoom and navigate - piano_model: the body tap was a click, and the objective was rewarding noise - score: add the sound panel and library modules - piano_model: a second engine, and the attack that finally sounded right - score: two instruments, reverb and brightness — and the rest of the panel gone - score model: a note remembers how it was struck, and the score remembers the pedal - score import: keep the velocities and the pedal the file was carrying - score playback: play the performance, not a flattened copy of it - score ui: the music list moves to the sidebar, and the view stops fighting itself - score: the application ships its font and eight performances - piano_model: a limiter that rides the music, so the knee stops shaping chords - piano_model: the forte bell was the treble's dynamic slope, and the bass was dying at its own prompt rate - piano_model: the bridge decides each partial's decay, and a fixed multiplier cannot say that - piano_model: each partial gets its own two coupled modes, from the eigen algebra - piano_model: a median that fell between the peaks made every bass partial a drain - score-ai: LocalBroker — the seam's in-process implementation over the session engine (aicore P8) - client + chat dispatcher: the dead wire comes out (aicore P7/P8) - score_pdf: the score model grew a pedal map — the pdf importer initialises it - libs: the zero-warning sweep — stitch casts say what they mean, xatlas keeps upstream's surface quietly - score app: the shipped-piece test speaks the PERFORMANCES table - zero-warning sweep, round three — the model lanes and the deep examples
87 lines
3.4 KiB
Rust
87 lines
3.4 KiB
Rust
// Proof that Piano::process never allocates: a counting global allocator
|
|
// wraps the system allocator for this whole test binary; after construction
|
|
// and warm-up, two seconds of heavy rendering (notes, re-strikes, pedal
|
|
// churn) must leave the allocation counter untouched.
|
|
//
|
|
// (Locks/IO: the render path calls no std sync or IO APIs at all — verified
|
|
// by review; the multicore path is offline-only and documented as such.
|
|
// Panics: every slice access in the render path is bounds-checked against
|
|
// preallocated fixed sizes, and the adversarial test in verify.rs exercises
|
|
// the edge cases.)
|
|
|
|
use makepad_piano_model::{Piano, PianoEvent, TimedEvent};
|
|
use std::alloc::{GlobalAlloc, Layout, System};
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
static ALLOCS: AtomicUsize = AtomicUsize::new(0);
|
|
static DEALLOCS: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
struct CountingAlloc;
|
|
|
|
unsafe impl GlobalAlloc for CountingAlloc {
|
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
|
ALLOCS.fetch_add(1, Ordering::Relaxed);
|
|
unsafe { System.alloc(layout) }
|
|
}
|
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
|
DEALLOCS.fetch_add(1, Ordering::Relaxed);
|
|
unsafe { System.dealloc(ptr, layout) }
|
|
}
|
|
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
|
|
ALLOCS.fetch_add(1, Ordering::Relaxed);
|
|
unsafe { System.realloc(ptr, layout, new_size) }
|
|
}
|
|
}
|
|
|
|
#[global_allocator]
|
|
static A: CountingAlloc = CountingAlloc;
|
|
|
|
#[test]
|
|
fn render_path_never_allocates() {
|
|
let mut piano = Piano::new(48000.0);
|
|
let mut out_l = vec![0.0f32; 512];
|
|
let mut out_r = vec![0.0f32; 512];
|
|
let mut events: Vec<TimedEvent> = Vec::with_capacity(64);
|
|
|
|
// Warm-up: touch every code path (strikes on every key, damper noise,
|
|
// sympathetic banks, voice sleep/wake) before counting.
|
|
for key in 21..=108u8 {
|
|
events.clear();
|
|
events.push(TimedEvent { offset: 0, event: PianoEvent::NoteOn { key, velocity: 100 } });
|
|
events.push(TimedEvent { offset: 256, event: PianoEvent::NoteOff { key } });
|
|
piano.process(&events, &mut out_l, &mut out_r);
|
|
}
|
|
for _ in 0..200 {
|
|
piano.process(&[], &mut out_l, &mut out_r);
|
|
}
|
|
|
|
let a0 = ALLOCS.load(Ordering::Relaxed);
|
|
let d0 = DEALLOCS.load(Ordering::Relaxed);
|
|
|
|
// Two seconds of dense playing through the counted section.
|
|
let mut key = 21u8;
|
|
for block in 0..188u32 {
|
|
events.clear();
|
|
if block % 2 == 0 {
|
|
events.push(TimedEvent { offset: (block % 512) & 511, event: PianoEvent::NoteOn { key, velocity: 127 } });
|
|
key = if key >= 108 { 21 } else { key + 1 };
|
|
}
|
|
if block % 3 == 0 {
|
|
events.push(TimedEvent {
|
|
offset: 511,
|
|
event: PianoEvent::Sustain { value: if block % 6 == 0 { 1.0 } else { 0.0 } },
|
|
});
|
|
}
|
|
if block % 7 == 0 {
|
|
events.push(TimedEvent { offset: 100, event: PianoEvent::NoteOff { key: 21 + (block as u8 % 88) } });
|
|
}
|
|
events.sort_by_key(|e| e.offset);
|
|
piano.process(&events, &mut out_l, &mut out_r);
|
|
}
|
|
|
|
let allocs = ALLOCS.load(Ordering::Relaxed) - a0;
|
|
let deallocs = DEALLOCS.load(Ordering::Relaxed) - d0;
|
|
assert_eq!(allocs, 0, "Piano::process allocated {allocs} times");
|
|
assert_eq!(deallocs, 0, "Piano::process deallocated {deallocs} times");
|
|
println!("render path: 0 allocations across 188 blocks of dense playing");
|
|
}
|