use crossbeam_channel::Sender; use params::{ParamId, ParamStore}; use vizia::prelude::*; use crate::widgets::piano::PianoKeyboard; pub const MOD_NSRC: usize = 19; pub const MOD_NDST: usize = 11; #[derive(Clone, Copy, Debug)] pub enum MidiMsg { NoteOn(u8, u8), NoteOff(u8), AllNotesOff, } #[derive(Clone, Copy, Debug, PartialEq, Data)] pub enum Panel { Voice, Effects, Matrix, } impl Panel { pub const ALL: &'static [Self] = &[Self::Voice, Self::Effects, Self::Matrix]; pub fn label(self) -> &'static str { match self { Self::Voice => "VOICE", Self::Effects => "EFFECTS", Self::Matrix => "MATRIX", } } } #[derive(Lens, Clone)] pub struct AppData { pub params: Vec, pub active_panel: Panel, pub preset_name: String, pub voice_count: u8, pub cpu_load: f32, pub host_bpm: f32, #[lens(ignore)] pub store: ParamStore, pub held_notes: Vec, pub octave: i32, pub mod_depths: Vec, pub fx_enabled: Vec, pub active_env: usize, pub active_lfo: usize, pub fx_selected: usize, #[lens(ignore)] pub midi_tx: Sender, } impl AppData { pub fn new(store: ParamStore, midi_tx: Sender) -> Self { let params = (0..ParamId::COUNT) .map(|i| store.get(unsafe { std::mem::transmute::(i) })) .collect(); Self { params, store, active_panel: Panel::Voice, preset_name: "Init".into(), voice_count: 0, cpu_load: 0.0, host_bpm: 120.0, held_notes: Vec::new(), octave: 4, mod_depths: vec![0.0; MOD_NSRC * MOD_NDST], fx_enabled: vec![true; 6], active_env: 0, active_lfo: 0, fx_selected: 0, midi_tx, } } } #[derive(Debug)] pub enum AppEvent { SetParam(ParamId, f32), SetParamRaw(ParamId, f32), SetPanel(Panel), UpdateMetrics { voices: u8, cpu: f32 }, NoteOn(u8, u8), NoteOff(u8), OctaveUp, OctaveDown, SetModDepth { src: usize, dst: usize, depth: f32 }, ToggleFx(usize), SelectEnv(usize), SelectLfo(usize), SelectFx(usize), } impl Model for AppData { fn event(&mut self, cx: &mut EventContext, event: &mut Event) { event.map(|e: &AppEvent, _| match e { AppEvent::SetParam(id, val) => { let v = val.clamp(0.0, 1.0); self.params[*id as usize] = v; self.store.set(*id, v); } AppEvent::SetParamRaw(id, val) => { self.params[*id as usize] = *val; self.store.set(*id, *val); } AppEvent::SetPanel(p) => self.active_panel = *p, AppEvent::SelectEnv(i) => self.active_env = *i, AppEvent::SelectLfo(i) => self.active_lfo = *i, AppEvent::SelectFx(i) => self.fx_selected = *i, AppEvent::UpdateMetrics { voices, cpu } => { self.voice_count = *voices; self.cpu_load = *cpu; } AppEvent::NoteOn(note, vel) => { if !self.held_notes.contains(note) { self.held_notes.push(*note); } let _ = self.midi_tx.try_send(MidiMsg::NoteOn(*note, *vel)); } AppEvent::NoteOff(note) => { self.held_notes.retain(|n| n != note); let _ = self.midi_tx.try_send(MidiMsg::NoteOff(*note)); } AppEvent::OctaveUp => self.octave = (self.octave + 1).min(8), AppEvent::OctaveDown => self.octave = (self.octave - 1).max(0), AppEvent::SetModDepth { src, dst, depth } => { let idx = src * MOD_NDST + dst; if idx < self.mod_depths.len() { self.mod_depths[idx] = depth.clamp(-1.0, 1.0); } } AppEvent::ToggleFx(s) => { if *s < self.fx_enabled.len() { self.fx_enabled[*s] = !self.fx_enabled[*s]; } } }); event.map(|we: &WindowEvent, _| { crate::widgets::piano::handle_kbd(cx, we, self.octave); }); } } pub fn build_root(cx: &mut Context) { VStack::new(cx, |cx| { crate::panels::header::build(cx); HStack::new(cx, |cx| { crate::panels::macro_bar::build(cx); Binding::new(cx, AppData::active_panel, |cx, p| { VStack::new(cx, |cx| match p.get(cx) { Panel::Voice => crate::panels::voice::build(cx), Panel::Effects => crate::panels::fx::build(cx), Panel::Matrix => crate::panels::mod_matrix::build(cx), }) .width(Stretch(1.0)) .height(Stretch(1.0)) .background_color(Color::rgb(12, 12, 20)); }); crate::panels::env_lfo_sidebar::build(cx); }) .height(Stretch(1.0)); PianoKeyboard::new(cx); }) .background_color(Color::rgb(10, 10, 16)) .width(Stretch(1.0)) .height(Stretch(1.0)); } fn tab_button(cx: &mut Context, p: Panel) { Binding::new(cx, AppData::active_panel, move |cx, active_lens| { let active = active_lens.get(cx) == p; Label::new(cx, p.label()) .class("top-tab") .checked(active) .on_press(move |cx| cx.emit(AppEvent::SetPanel(p))) .height(Stretch(1.0)); }); }