draw_metronome() renders the screen for any parsed track: track name + big BPM, play/stop transport, and the polymeter lane grid — per-lane beat cells coloured by level (accent amber / normal cyan / ghost purple / rest dark), playhead highlight, beat gridlines, poly (~) marker. Pure no_std view over borrowed data (LaneView/ Screen) so the firmware build stays allocator-free. uisim now parses a real track (track-format) and renders draw_metronome to PNG — iterate the UI on the host, no bench. Firmware still draws the bring-up diagnostic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
2.2 KiB
Rust
79 lines
2.2 KiB
Rust
//! 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,
|
||
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}");
|
||
}
|