metronome/rust/uisim/src/main.rs
Me Here cb54b4d689 Preserve notation + grammar feature work (verified complete + green)
The parallel agent's full session, committed now that it's solo:
- Grammar: flam/drag/roll ornaments (f/F d/D z/Z, per-lane orns channel) across
  src/engine.js, pico-cp/pico-explorer/pico-scroll app.py, pico/main.py, rust/track-format,
  + golden vectors / conformance (tests/, rust/track-format/tests).
- Live-sync deep-sync: SysEx 0x44 SLSYNC + 0x45 LOGSYNC (docs/livesync-protocol.md, src/livesync.js).
- PM_E-2 notation: web engine (pm_e-2.html, build/deploy/index/embed wiring) + Rust device port
  (pm-ui draw_notation rewrite + LaneView.groups, pm-kit ViewMode, uisim notesim).

Verified: node tests/run.mjs 47 pass / 1 known; ./rust/run.sh green; pm-kit firmware + uisim compile.
2026-06-02 13:45:26 -05:00

81 lines
2.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Render the metronome UI (pm-ui) for a real parsed track onto a 320×480 framebuffer and save a
//! PNG — no device. `cargo run` [program-string]. Lets the UI be developed/reviewed off the bench.
use embedded_graphics::{pixelcolor::Rgb565, prelude::*};
use pm_ui::{LaneView, Screen};
const W: u32 = 320;
const H: u32 = 480;
struct Fb {
px: Vec<Rgb565>,
}
impl Fb {
fn new() -> Self {
Fb { px: vec![Rgb565::BLACK; (W * H) as usize] }
}
}
impl DrawTarget for Fb {
type Color = Rgb565;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(p, c) in pixels {
if p.x >= 0 && p.y >= 0 && (p.x as u32) < W && (p.y as u32) < H {
self.px[(p.y as u32 * W + p.x as u32) as usize] = c;
}
}
Ok(())
}
}
impl OriginDimensions for Fb {
fn size(&self) -> Size {
Size::new(W, H)
}
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let prog = args
.get(1)
.cloned()
.unwrap_or_else(|| "t128;kick:4=Xxxx;snare:4=.x.x;hatClosed:4/2;ride:4/2s=X.x.x.x.;cowbell:3~".into());
let track = track_format::parse(&prog);
let lanes: Vec<LaneView> = track
.lanes
.iter()
.map(|l| LaneView {
name: &l.sound,
levels: &l.levels,
orns: &l.orns,
groups: &l.groups,
beats: l.groups.iter().sum::<u32>().min(255) as u8,
poly: l.poly,
muted: l.mute,
})
.collect();
let screen = Screen {
name: "Four-on-the-floor",
bpm: track.bpm,
playing: true,
phase: 0.30,
lanes: &lanes,
};
let mut fb = Fb::new();
pm_ui::draw_metronome(&mut fb, &screen).unwrap();
let img = image::RgbImage::from_fn(W, H, |x, y| {
let c = fb.px[(y * W + x) as usize];
let r = (c.r() << 3) | (c.r() >> 2);
let g = (c.g() << 2) | (c.g() >> 4);
let b = (c.b() << 3) | (c.b() >> 2);
image::Rgb([r, g, b])
});
let out = "pm-kit-ui.png";
img.save(out).unwrap();
println!("wrote {out} ({W}x{H}) for: {prog}");
}