diff --git a/rust/pm-kit/src/main.rs b/rust/pm-kit/src/main.rs index ee9cae0..3bdd3c7 100644 --- a/rust/pm-kit/src/main.rs +++ b/rust/pm-kit/src/main.rs @@ -150,144 +150,23 @@ fn main() -> ! { .init(&mut timer) .unwrap(); - // ---- inputs + speaker ---- - let mut btn_a = pins.gpio15.into_pull_up_input(); // A = play/stop - let mut btn_b = pins.gpio14.into_pull_up_input(); // B = grid/notation view - let mut adc = hal::adc::Adc::new(pac.ADC, &mut pac.RESETS); - let mut joy_x = hal::adc::AdcPin::new(pins.gpio26).unwrap(); - let mut joy_y = hal::adc::AdcPin::new(pins.gpio27).unwrap(); - let pwm_slices = hal::pwm::Slices::new(pac.PWM, &mut pac.RESETS); - let mut spk = pwm_slices.pwm6; // GP13 = PWM slice 6, channel B - spk.set_div_int(125); - spk.set_top(600); // ~2 kHz click - spk.enable(); - spk.channel_b.output_to(pins.gpio13); - - // ---- built-in grooves ---- - const GROOVES: [&str; 4] = [ - "t120;kick:4=X.x.;snare:4=.X.X;hatClosed:4/2=xxxxxxxx", - "t92;kick:4/3=X....x...x..;snare:4/3=..gg.gX.gg.g;hatClosed:4/3=X.xX.xX.xX.x", - "t140;kick:4=X..x;snare:4=.X.X;hatClosed:4/4=xxxxxxxxxxxxxxxx", - "t100;kick:4=Xxxx;claves:5=Xxxxx~", - ]; - const NAMES: [&str; 4] = ["Four on the floor", "Half-time shuffle", "Driving 16ths", "5 over 4"]; - - let mut idx = 0usize; - let mut track = track_format::parse(GROOVES[idx]); - let mut tempo: i64 = track.bpm; - let mut playing = true; - let mut notation = false; - - let mut bar_start = timer.get_counter().ticks(); - let mut last_step = usize::MAX; - let mut click_off_us: u64 = 0; - let (mut pa, mut pb) = (false, false); - let mut joy_zone = 0i8; - let mut full_redraw = true; - let mut last_draw_us = 0u64; + // ---- MINIMAL ISOLATION ---- + // Heap is initialised above. Exercise the allocator once (parse), then just draw the + // confirmed-working pattern in a loop. No inputs/audio/clock. If the screen shows blue + + // corners → heap + parse + display are all fine and the bug is in the metronome loop logic. + // If still black → the heap/parse path breaks the display. + let _t = track_format::parse("t120;kick:4=Xxxx;snare:4=.X.X"); let mut hb = false; let mut hb_us = 0u64; led.set_low().unwrap(); - loop { + pm_ui::draw_ui(&mut display).ok(); let now = timer.get_counter().ticks(); - - // ---- inputs ---- - let a = btn_a.is_low().unwrap_or(false); - let b = btn_b.is_low().unwrap_or(false); - if a && !pa { - playing = !playing; - if playing { - bar_start = now; - last_step = usize::MAX; - } - full_redraw = true; - } - if b && !pb { - notation = !notation; - full_redraw = true; - } - pa = a; - pb = b; - - let jx = adc.read(&mut joy_x).unwrap_or(2048); - let jy = adc.read(&mut joy_y).unwrap_or(2048); - // rotate the joystick reading 90° CCW for control - let rx = jy as i32; - let ry = 4095 - jx as i32; - let zone: i8 = if ry > 3200 { 1 } else if ry < 900 { 2 } else if rx > 3200 { 3 } else if rx < 900 { 4 } else { 0 }; - if zone != 0 && joy_zone == 0 { - match zone { - 1 => tempo = (tempo + 4).min(300), // up → tempo+ - 2 => tempo = (tempo - 4).max(30), // down → tempo- - 3 => { - idx = (idx + 1) % GROOVES.len(); // right → next groove - track = track_format::parse(GROOVES[idx]); - tempo = track.bpm; - bar_start = now; - last_step = usize::MAX; - } - 4 => { - idx = (idx + GROOVES.len() - 1) % GROOVES.len(); // left → prev - track = track_format::parse(GROOVES[idx]); - tempo = track.bpm; - bar_start = now; - last_step = usize::MAX; - } - _ => {} - } - full_redraw = true; - } - joy_zone = zone; - - // ---- clock ---- - let master = &track.lanes[0]; - let msteps = master.levels.len().max(1) as u64; - let beats = master.groups.iter().map(|&g| g as u64).sum::().max(1); - let bar_us = 60_000_000u64 * beats / tempo.max(1) as u64; - let step_us = (bar_us / msteps).max(1); - let elapsed = now.wrapping_sub(bar_start) % bar_us; - let cur_step = (elapsed / step_us) as usize; - let phase = elapsed as f32 / bar_us as f32; - - // ---- audio: click on a new step that has a hit ---- - if playing && cur_step != last_step { - let lvl = master.levels[cur_step.min(msteps as usize - 1)]; - if lvl > 0 { - let _ = spk.channel_b.set_duty_cycle(if lvl == 2 { 380 } else { 240 }); - click_off_us = now + if lvl == 2 { 28_000 } else { 14_000 }; - } - last_step = cur_step; - } - if now >= click_off_us { - let _ = spk.channel_b.set_duty_cycle(0); - } - - // ---- draw: on change, and periodically (so a draw lost right after init reappears, and - // the playhead advances). ~7 fps; partial/playhead-only redraw is the next optimization. ---- - if full_redraw || now.wrapping_sub(last_draw_us) > 140_000 { - // DIAGNOSTIC: draw the confirmed-working bring-up pattern instead of the metronome, - // to isolate whether the blank screen is draw_metronome or the heap/display path. - // If you see blue + 4 colored corners + "TL"/"PMK" → display+heap are fine and the - // bug is in draw_metronome. If still black → it's the heap/display, not the drawing. - let _ = (notation, idx, tempo, phase, &track); // keep state alive while diagnosing - pm_ui::draw_ui(&mut display).ok(); - full_redraw = false; - last_draw_us = now; - } - - // heartbeat LED (~1 Hz) if now.wrapping_sub(hb_us) > 500_000 { hb = !hb; let _ = if hb { led.set_high() } else { led.set_low() }; hb_us = now; } - timer.delay_ms(8); + timer.delay_ms(50); } } - -/// picotool metadata (visible via `picotool info`). -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 1] = - [hal::binary_info::rp_program_name!(c"pm-kit display")];