//! 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, } 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(&mut self, pixels: I) -> Result<(), Self::Error> where I: IntoIterator>, { 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 = 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 = track .lanes .iter() .map(|l| LaneView { name: &l.sound, levels: &l.levels, orns: &l.orns, groups: &l.groups, beats: l.groups.iter().sum::().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}"); }