Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ keyboard_keyboard/kicad/~keyboard_keyboard.kicad_sch.lck
.DS_Store
keyboard_keyboard/.DS_Store
.DS_Store
keyboard_keyboard/kicad/\#auto_saved_files\#
keyboard_keyboard/kicad/~keyboard_keyboard.kicad_pcb.lck
2 changes: 1 addition & 1 deletion keyboard_keyboard/code/Embed.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[default.general]
chip = "stm32h750v"
chip = "STM32H750VBTx"

[default.rtt]
enabled = true
Expand Down
3 changes: 2 additions & 1 deletion keyboard_keyboard/code/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ pub const PITCH_BEND_INTERVAL_MS: u32 = 5;
pub const VIBRATO_A: usize = 76; // HE77 → vibrato depth
pub const VIBRATO_B: usize = 78; // HE79 → vibrato depth
pub const VIBRATO_MAX_DELTA: u16 = 300;
pub const VIBRATO_DEAD_ZONE: u16 = 30; // noise floor below which output = 0
pub const VIBRATO_DEAD_ZONE: u16 = 100; // press threshold below which output = 0
// (was 30 — vibrato triggered far too easily)
pub const VIBRATO_HYSTERESIS: u8 = 2;
pub const VIBRATO_INTERVAL_MS: u32 = 10;

Expand Down
10 changes: 8 additions & 2 deletions keyboard_keyboard/code/src/display/screens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,15 @@ pub fn draw_settings(disp: &mut LcdDisplay, selected: usize, settings: &Settings
.draw(disp)
.ok();

// Value: right-aligned
// Value: right-aligned (the vibrato toggle shows ON/OFF, not 0/1)
let mut val_str: String<8> = String::new();
write!(val_str, "{}", value).ok();
if item_idx == crate::settings::VIBRATO_SETTING {
write!(val_str, "{}", if value != 0 { "ON" } else { "OFF" }).ok();
} else if item_idx == crate::settings::RESET_DEFAULTS_SETTING {
write!(val_str, "UP").ok();
} else {
write!(val_str, "{}", value).ok();
}
Text::with_alignment(
val_str.as_str(),
Point::new(127, y),
Expand Down
105 changes: 92 additions & 13 deletions keyboard_keyboard/code/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod display;
mod hardware;
mod midi;
mod settings;
mod settings_storage;
mod switch;
mod types;

Expand All @@ -25,7 +26,7 @@ mod app {
i2c_bus_recovery, read_all_adcs, set_decoder, set_mux_channel, AdcPins, MuxRaw,
};
use crate::midi::MidiSender;
use crate::settings::{Settings, NUM_SETTINGS_ITEMS};
use crate::settings::{Settings, NUM_SETTINGS_ITEMS, RESET_DEFAULTS_SETTING};
use crate::switch::{ChannelFilter, SwitchEvent, SwitchState};
use crate::types::{DisplayState, LastEvent, LcdDisplay};

Expand Down Expand Up @@ -68,12 +69,15 @@ mod app {
settings_active: bool,
settings_selected: usize,
recalibrate_pending: bool,
settings_changed: bool,
factory_reset_pending: bool,
}

// ── Local resources ───────────────────────────────────────────────────────
#[local]
struct Local {
audio: audio::Audio,
flash: libdaisy::flash::Flash,
adc: Adc<stm32::ADC1, adc::Enabled>,
adc_pins: AdcPins,
enb_a: Daisy4<Output<PushPull>>, // U1 A0 (ENB_A)
Expand Down Expand Up @@ -113,6 +117,10 @@ mod app {

let ccdr = system::System::init_clocks(device.PWR, device.RCC, &device.SYSCFG);
let mut system = libdaisy::system_init!(core, device, ccdr, BLOCK_SIZE);
let settings = crate::settings_storage::load(&mut system.flash).unwrap_or_else(|| {
info!("no valid saved settings; using defaults");
Settings::default()
});

let mut s0 = system
.gpio
Expand Down Expand Up @@ -325,6 +333,9 @@ mod app {
"keyboard_keyboard ready: {} switches, {} muxes",
NUM_SWITCHES, NUM_MUXES
);
let mut display_state = DisplayState::new();
display_state.melody_channel = settings.melody_channel;
display_state.drum_channel = settings.drum_channel;

(
Shared {
Expand All @@ -333,15 +344,18 @@ mod app {
baselines,
event_queue: heapless::spsc::Queue::new(),
midi_tx_flag: false,
display_state: DisplayState::new(),
display_state,
splash_done: false,
settings: Settings::default(),
settings,
settings_active: false,
settings_selected: 0,
recalibrate_pending: false,
settings_changed: false,
factory_reset_pending: false,
},
Local {
audio: system.audio,
flash: system.flash,
adc,
adc_pins,
enb_a,
Expand Down Expand Up @@ -433,9 +447,12 @@ mod app {
last_pitch_bend, last_vibrato_cc, led1, led2, led3, midi_rx,
led1_off_at, led2_off_at,
recalibrate_show_until: u32 = 0,
recalibrate_flashing: bool = false],
recalibrate_flashing: bool = false,
reset_hold_ms: u16 = 0,
reset_armed: bool = true],
shared = [tick_ms, switch_states, baselines, event_queue, midi_tx_flag, splash_done,
settings_active, recalibrate_pending, display_state],
settings_active, settings, recalibrate_pending, display_state,
factory_reset_pending],
priority = 15
)]
fn timer_handler(mut ctx: timer_handler::Context) {
Expand All @@ -459,6 +476,7 @@ mod app {
// touched by per-tick tracking.
let anchor_baselines = ctx.shared.baselines.lock(|b| *b);
let settings_active = ctx.shared.settings_active.lock(|a| *a);
let vibrato_enabled = ctx.shared.settings.lock(|s| s.vibrato_enabled);
let mut pending: heapless::Vec<(usize, SwitchEvent), 32> = heapless::Vec::new();

// ── ADC scan ──────────────────────────────────────────────────────────
Expand Down Expand Up @@ -547,6 +565,27 @@ mod app {
}
});

// Emergency recovery: after the splash, hold Settings (HE74) and All Notes
// Off (HE76) together for three seconds. Requiring a post-calibration hold
// avoids treating keys held during power-on calibration as a reset request.
let splash_done = ctx.shared.splash_done.lock(|done| *done);
let reset_keys_down = ctx.shared.switch_states.lock(|states| {
!states[SETTINGS_OPEN].is_idle() && !states[ALL_NOTES_OFF_KEY].is_idle()
});
if splash_done && reset_keys_down && *ctx.local.reset_armed {
*ctx.local.reset_hold_ms = ctx.local.reset_hold_ms.saturating_add(1);
if *ctx.local.reset_hold_ms >= 3000 {
*ctx.local.reset_armed = false;
ctx.shared
.factory_reset_pending
.lock(|pending| *pending = true);
process_events::spawn().ok();
}
} else if !reset_keys_down {
*ctx.local.reset_hold_ms = 0;
*ctx.local.reset_armed = true;
}

// ── Pitch bend (rate-limited, only when settings is closed) ───────────
if !settings_active && now % PITCH_BEND_INTERVAL_MS == 0 {
let delta_down = pb_filt_down.abs_diff(ctx.local.dynamic_baselines[PITCH_BEND_DOWN]);
Expand Down Expand Up @@ -593,8 +632,8 @@ mod app {
);
}

// ── Vibrato → CC1 (dead zone + rate-limited, only when settings closed) ─
if !settings_active && now % VIBRATO_INTERVAL_MS == 0 {
// ── Vibrato → CC1 (dead zone + rate-limited, only when enabled & settings closed) ─
if vibrato_enabled && !settings_active && now % VIBRATO_INTERVAL_MS == 0 {
let max_delta = vib_filt_a
.abs_diff(ctx.local.dynamic_baselines[VIBRATO_A])
.max(vib_filt_b.abs_diff(ctx.local.dynamic_baselines[VIBRATO_B]))
Expand Down Expand Up @@ -704,8 +743,9 @@ mod app {
// ── MIDI output ───────────────────────────────────────────────────────────
#[task(
shared = [event_queue, midi_tx_flag, display_state, splash_done,
settings, settings_active, settings_selected, recalibrate_pending],
local = [midi_sender],
settings, settings_active, settings_selected, recalibrate_pending,
settings_changed, factory_reset_pending],
local = [midi_sender, flash],
priority = 2,
capacity = 32
)]
Expand All @@ -721,6 +761,17 @@ mod app {
let mut did_send = false;
let mut new_display_event: Option<LastEvent> = None;
let mut new_volume: Option<u8> = None;
let factory_reset = ctx
.shared
.factory_reset_pending
.lock(|pending| core::mem::replace(pending, false));
if factory_reset {
settings = Settings::default();
active = false;
selected = 0;
settings_dirty = true;
nav_dirty = true;
}

ctx.shared.event_queue.lock(|queue| {
while let Some((switch_idx, event)) = queue.dequeue() {
Expand Down Expand Up @@ -803,14 +854,21 @@ mod app {
nav_dirty = true;
}
SETTINGS_VAL_UP => {
settings.adjust(selected, 1);
if selected == RESET_DEFAULTS_SETTING {
settings = Settings::default();
info!("factory defaults selected");
} else {
settings.adjust(selected, 1);
}
settings_dirty = true;
nav_dirty = true;
}
SETTINGS_VAL_DOWN => {
settings.adjust(selected, -1);
settings_dirty = true;
nav_dirty = true;
if selected != RESET_DEFAULTS_SETTING {
settings.adjust(selected, -1);
settings_dirty = true;
nav_dirty = true;
}
}
_ => {}
}
Expand Down Expand Up @@ -880,6 +938,15 @@ mod app {
// ── Write back mutations ───────────────────────────────────────────────
if settings_dirty {
ctx.shared.settings.lock(|s| *s = settings);
ctx.shared.settings_changed.lock(|changed| *changed = true);
}
if factory_reset {
if crate::settings_storage::save(ctx.local.flash, settings) {
info!("factory defaults restored and saved");
ctx.shared.settings_changed.lock(|changed| *changed = false);
} else {
warn!("factory reset save failed");
}
}
if nav_dirty {
ctx.shared.settings_active.lock(|a| *a = active);
Expand All @@ -899,6 +966,18 @@ mod app {
did_send = true;

if !active {
let should_save = ctx
.shared
.settings_changed
.lock(|changed| core::mem::replace(changed, false));
if should_save {
if crate::settings_storage::save(ctx.local.flash, settings) {
info!("settings saved");
} else {
warn!("settings save failed");
ctx.shared.settings_changed.lock(|changed| *changed = true);
}
}
// Settings just closed — push updated parameters to the synth.
ctx.local.midi_sender.set_channel(settings.melody_channel);
ctx.local
Expand Down
35 changes: 27 additions & 8 deletions keyboard_keyboard/code/src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
pub const NUM_SETTINGS_ITEMS: usize = 6;
pub const NUM_SETTINGS_ITEMS: usize = 8;

/// Menu index of the VIBRATO on/off toggle (rendered as ON/OFF, not a number).
pub const VIBRATO_SETTING: usize = 6;
pub const RESET_DEFAULTS_SETTING: usize = 7;

pub struct SettingsItem {
pub name: &'static str,
Expand Down Expand Up @@ -37,27 +41,39 @@ pub const SETTINGS_ITEMS: [SettingsItem; NUM_SETTINGS_ITEMS] = [
min: 0,
max: 127,
},
SettingsItem {
name: "VIBRATO",
min: 0,
max: 1,
},
SettingsItem {
name: "RESET DEFAULTS",
min: 0,
max: 0,
},
];

#[derive(Clone, Copy, Debug)]
pub struct Settings {
pub melody_channel: u8, // 0-indexed (0–15), displayed as 1–16
pub drum_channel: u8, // 0-indexed (0–15), displayed as 1–16
pub octave: i8, // 0–8; offset = (octave - 4) * 12
pub pitch_bend_range: u8, // semitones 1–12
pub melody_program: u8, // 0–127, sent as PC on melody channel when settings closes
pub drum_program: u8, // 0–127, sent as PC on drum channel when settings closes
pub melody_channel: u8, // 0-indexed (0–15), displayed as 1–16
pub drum_channel: u8, // 0-indexed (0–15), displayed as 1–16
pub octave: i8, // 0–8; offset = (octave - 4) * 12
pub pitch_bend_range: u8, // semitones 1–12
pub melody_program: u8, // 0–127, sent as PC on melody channel when settings closes
pub drum_program: u8, // 0–127, sent as PC on drum channel when settings closes
pub vibrato_enabled: bool, // off by default — vibrato keys send CC1 only when on
}

impl Settings {
pub const fn default() -> Self {
Self {
melody_channel: 0,
melody_channel: 2,
drum_channel: 9,
octave: 2,
pitch_bend_range: 2,
melody_program: 0,
drum_program: 0,
vibrato_enabled: false,
}
}

Expand All @@ -70,6 +86,8 @@ impl Settings {
3 => self.pitch_bend_range as i16,
4 => self.melody_program as i16,
5 => self.drum_program as i16,
6 => self.vibrato_enabled as i16,
7 => 0,
_ => 0,
}
}
Expand All @@ -85,6 +103,7 @@ impl Settings {
3 => self.pitch_bend_range = v as u8,
4 => self.melody_program = v as u8,
5 => self.drum_program = v as u8,
6 => self.vibrato_enabled = v != 0,
_ => {}
}
}
Expand Down
Loading
Loading