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
83 lines
3.3 KiB
Rust
83 lines
3.3 KiB
Rust
// Proof that LearnedPiano::process never allocates, in its own test binary
|
|
// (a counting global allocator is per-process, and the default parallel
|
|
// test runner would bleed other tests' allocations into the count — the
|
|
// same reason rt_safety.rs stands alone for the physical engine).
|
|
|
|
use makepad_piano_model::learned::LearnedPiano;
|
|
use makepad_piano_model::{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 learned_render_path_never_allocates() {
|
|
let mut p = LearnedPiano::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 over every key, both slots (re-strike), pedal churn.
|
|
for key in 21..=108u8 {
|
|
events.clear();
|
|
events.push(TimedEvent { offset: 0, event: PianoEvent::NoteOn { key, velocity: 100 } });
|
|
events.push(TimedEvent { offset: 200, event: PianoEvent::NoteOn { key, velocity: 60 } });
|
|
events.push(TimedEvent { offset: 400, event: PianoEvent::NoteOff { key } });
|
|
p.process(&events, &mut out_l, &mut out_r);
|
|
}
|
|
for _ in 0..200 {
|
|
p.process(&[], &mut out_l, &mut out_r);
|
|
}
|
|
|
|
let a0 = ALLOCS.load(Ordering::Relaxed);
|
|
let d0 = DEALLOCS.load(Ordering::Relaxed);
|
|
|
|
let mut key = 21u8;
|
|
for block in 0..188u32 {
|
|
events.clear();
|
|
if block % 2 == 0 {
|
|
events.push(TimedEvent { offset: block % 512, 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 % 5 == 0 {
|
|
events.push(TimedEvent { offset: 200, event: PianoEvent::SoftPedal { on: block % 10 == 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);
|
|
p.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, "LearnedPiano::process allocated {allocs} times");
|
|
assert_eq!(deallocs, 0, "LearnedPiano::process deallocated {deallocs} times");
|
|
println!("learned render path: 0 allocations across 188 blocks of dense playing");
|
|
}
|