1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum FxType {
7 Reverb,
8 Delay,
9 Gate,
10 Eq,
11 Limiter,
12 Compressor,
13}
14
15impl FxType {
16 pub fn label(self) -> &'static str {
17 match self {
18 Self::Reverb => "reverb",
19 Self::Delay => "delay",
20 Self::Gate => "gate",
21 Self::Eq => "eq",
22 Self::Limiter => "limiter",
23 Self::Compressor => "comp",
24 }
25 }
26
27 pub const ALL: &[FxType] = &[
28 Self::Reverb, Self::Delay, Self::Gate, Self::Eq, Self::Limiter, Self::Compressor,
29 ];
30}
31
32#[derive(Debug, Clone)]
34pub struct FxInstance {
35 pub fx_type: FxType,
36 pub enabled: bool,
37 pub params: Vec<(String, f32)>,
39}
40
41impl FxInstance {
42 pub fn new(fx_type: FxType) -> Self {
43 let params = match fx_type {
44 FxType::Reverb => vec![
45 ("mix".into(), 0.3), ("decay".into(), 0.5), ("size".into(), 0.6),
46 ],
47 FxType::Delay => vec![
48 ("time".into(), 0.4), ("feedback".into(), 0.3), ("mix".into(), 0.25),
49 ],
50 FxType::Gate => vec![
51 ("thresh".into(), 0.5), ("attack".into(), 0.1), ("release".into(), 0.3),
52 ],
53 FxType::Eq => vec![
54 ("low".into(), 0.5), ("mid".into(), 0.5), ("high".into(), 0.5),
55 ],
56 FxType::Limiter => vec![
57 ("thresh".into(), 0.8), ("release".into(), 0.2),
58 ],
59 FxType::Compressor => vec![
60 ("thresh".into(), 0.6), ("ratio".into(), 0.4), ("attack".into(), 0.1),
61 ("release".into(), 0.3),
62 ],
63 };
64 Self { fx_type, enabled: true, params }
65 }
66}
67
68#[derive(Debug)]
70pub struct FxMenu {
71 pub open: bool,
72 pub cursor: usize,
73}
74
75impl Default for FxMenu {
76 fn default() -> Self { Self::new() }
77}
78
79impl FxMenu {
80 pub fn new() -> Self {
81 Self { open: false, cursor: 0 }
82 }
83
84 pub fn item_count(&self) -> usize {
85 FxType::ALL.len()
86 }
87
88 pub fn move_up(&mut self) {
89 if self.cursor > 0 { self.cursor -= 1; }
90 }
91
92 pub fn move_down(&mut self) {
93 if self.cursor + 1 < self.item_count() { self.cursor += 1; }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum InstrumentType {
101 Synth,
102 DrumRack,
103 DX7,
104 Jupiter8,
105 Odyssey,
106 Juno60,
107 Sampler,
108}
109
110impl InstrumentType {
111 pub fn label(self) -> &'static str {
112 match self {
113 Self::Synth => "Phosphor Synth",
114 Self::DrumRack => "Drum Rack",
115 Self::DX7 => "DX7",
116 Self::Jupiter8 => "Jupiter-8",
117 Self::Odyssey => "Odyssey",
118 Self::Juno60 => "Juno-60",
119 Self::Sampler => "Sampler",
120 }
121 }
122
123 pub fn description(self) -> &'static str {
124 match self {
125 Self::Synth => "polyphonic subtractive synthesizer",
126 Self::DrumRack => "drum machine with sample pads",
127 Self::DX7 => "6-operator FM synthesizer",
128 Self::Jupiter8 => "dual-VCO analog poly synthesizer",
129 Self::Odyssey => "duophonic synth with 3 filter types",
130 Self::Juno60 => "single-DCO poly with BBD chorus",
131 Self::Sampler => "sample-based instrument",
132 }
133 }
134
135 pub const ALL: &[InstrumentType] = &[Self::Synth, Self::DrumRack, Self::DX7, Self::Jupiter8, Self::Odyssey, Self::Juno60, Self::Sampler];
136}
137
138#[derive(Debug)]
139pub struct InstrumentModal {
140 pub open: bool,
141 pub cursor: usize,
142}
143
144impl Default for InstrumentModal {
145 fn default() -> Self { Self::new() }
146}
147
148impl InstrumentModal {
149 pub fn new() -> Self {
150 Self { open: false, cursor: 0 }
151 }
152
153 pub fn move_up(&mut self) {
154 if self.cursor > 0 { self.cursor -= 1; }
155 }
156
157 pub fn move_down(&mut self) {
158 if self.cursor + 1 < InstrumentType::ALL.len() { self.cursor += 1; }
159 }
160
161 pub fn selected(&self) -> InstrumentType {
162 InstrumentType::ALL[self.cursor]
163 }
164}
165
166#[derive(Debug)]
175pub struct PresetModal {
176 pub open: bool,
177 pub instrument: Option<InstrumentType>,
179 pub track_idx: usize,
182 pub cursor: usize,
183 pub entries: Vec<String>,
185 pub error: Option<String>,
187 pub pending_name: String,
189}
190
191impl Default for PresetModal {
192 fn default() -> Self { Self::new() }
193}
194
195impl PresetModal {
196 pub const SAVE_ROW: usize = 0;
198
199 pub fn new() -> Self {
200 Self {
201 open: false,
202 instrument: None,
203 track_idx: 0,
204 cursor: 0,
205 entries: Vec::new(),
206 error: None,
207 pending_name: String::new(),
208 }
209 }
210
211 pub fn show(&mut self, instrument: InstrumentType, track_idx: usize, entries: Vec<String>) {
212 self.open = true;
213 self.instrument = Some(instrument);
214 self.track_idx = track_idx;
215 self.cursor = 0;
216 self.entries = entries;
217 self.error = None;
218 self.pending_name.clear();
219 }
220
221 pub fn close(&mut self) {
222 self.open = false;
223 self.entries.clear();
224 self.error = None;
225 self.pending_name.clear();
226 self.cursor = 0;
227 }
228
229 pub fn item_count(&self) -> usize { self.entries.len() + 1 }
231
232 pub fn move_up(&mut self) {
233 if self.cursor > 0 { self.cursor -= 1; }
234 }
235
236 pub fn move_down(&mut self) {
237 if self.cursor + 1 < self.item_count() { self.cursor += 1; }
238 }
239
240 pub fn selected_preset(&self) -> Option<usize> {
242 self.cursor.checked_sub(1).filter(|i| *i < self.entries.len())
243 }
244
245 pub fn selected_name(&self) -> Option<&str> {
247 self.selected_preset().map(|i| self.entries[i].as_str())
248 }
249
250 pub fn set_entries(&mut self, entries: Vec<String>) {
253 self.entries = entries;
254 let max = self.item_count() - 1;
255 if self.cursor > max { self.cursor = max; }
256 }
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum SpaceAction {
264 PlayPause,
265 ToggleRecord,
266 ToggleLoop,
267 ToggleMetronome,
268 Panic,
269 Save,
270 Open,
271 AddInstrument,
272 Delete,
273 CycleTheme,
274 NewTrack,
275 EditMode,
276 Quantize,
277 Presets,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub enum ConfirmKind {
284 DeleteTrack,
285 DeleteClip,
286 DeletePreset,
287 OverwritePreset,
289}
290
291#[derive(Debug)]
292pub struct ConfirmModal {
293 pub open: bool,
294 pub kind: ConfirmKind,
295 pub message: String,
296}
297
298impl Default for ConfirmModal {
299 fn default() -> Self { Self::new() }
300}
301
302impl ConfirmModal {
303 pub fn new() -> Self {
304 Self { open: false, kind: ConfirmKind::DeleteTrack, message: String::new() }
305 }
306
307 pub fn show(&mut self, kind: ConfirmKind, message: &str) {
308 self.open = true;
309 self.kind = kind;
310 self.message = message.to_string();
311 }
312
313 pub fn close(&mut self) {
314 self.open = false;
315 self.message.clear();
316 }
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq)]
322pub enum InputModalKind {
323 SaveAs,
324 Open,
325 PresetName,
327}
328
329#[derive(Debug)]
330pub struct InputModal {
331 pub open: bool,
332 pub kind: InputModalKind,
333 pub buffer: String,
334 pub cursor: usize,
335}
336
337impl Default for InputModal {
338 fn default() -> Self { Self::new() }
339}
340
341impl InputModal {
342 pub fn new() -> Self {
343 Self { open: false, kind: InputModalKind::SaveAs, buffer: String::new(), cursor: 0 }
344 }
345
346 pub fn open_save(&mut self, default_name: &str) {
347 self.open = true;
348 self.kind = InputModalKind::SaveAs;
349 self.buffer = format!("sessions/{default_name}");
350 self.cursor = self.buffer.len();
351 }
352
353 pub fn open_load(&mut self) {
354 self.open = true;
355 self.kind = InputModalKind::Open;
356 self.buffer = "sessions/".to_string();
357 self.cursor = self.buffer.len();
358 }
359
360 pub fn open_preset_name(&mut self) {
364 self.open = true;
365 self.kind = InputModalKind::PresetName;
366 self.buffer.clear();
367 self.cursor = 0;
368 }
369
370 pub fn type_char(&mut self, ch: char) {
371 self.buffer.insert(self.cursor, ch);
372 self.cursor += 1;
373 }
374
375 pub fn backspace(&mut self) {
376 if self.cursor > 0 {
377 self.cursor -= 1;
378 self.buffer.remove(self.cursor);
379 }
380 }
381
382 pub fn delete(&mut self) {
383 if self.cursor < self.buffer.len() {
384 self.buffer.remove(self.cursor);
385 }
386 }
387
388 pub fn move_left(&mut self) {
389 if self.cursor > 0 { self.cursor -= 1; }
390 }
391
392 pub fn move_right(&mut self) {
393 if self.cursor < self.buffer.len() { self.cursor += 1; }
394 }
395
396 pub fn move_home(&mut self) {
397 self.cursor = 0;
398 }
399
400 pub fn move_end(&mut self) {
401 self.cursor = self.buffer.len();
402 }
403
404 pub fn close(&mut self) {
405 self.open = false;
406 self.buffer.clear();
407 self.cursor = 0;
408 }
409
410 pub fn value(&self) -> &str {
411 &self.buffer
412 }
413}
414
415#[derive(Debug)]
418pub struct SpaceMenu {
419 pub open: bool,
420 pub cursor: usize,
421 pub section: SpaceMenuSection,
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
426pub enum SpaceMenuSection {
427 Actions,
429 Help,
431}
432
433impl Default for SpaceMenu {
434 fn default() -> Self { Self::new() }
435}
436
437impl SpaceMenu {
438 pub fn new() -> Self {
439 Self { open: false, cursor: 0, section: SpaceMenuSection::Actions }
440 }
441
442 pub fn toggle(&mut self) {
443 self.open = !self.open;
444 if self.open { self.cursor = 0; self.section = SpaceMenuSection::Actions; }
445 }
446
447 pub fn move_up(&mut self) {
448 if self.cursor > 0 { self.cursor -= 1; }
449 }
450
451 pub fn move_down(&mut self) {
452 let max = self.item_count();
453 if self.cursor + 1 < max { self.cursor += 1; }
454 }
455
456 pub fn switch_section(&mut self) {
457 self.section = match self.section {
458 SpaceMenuSection::Actions => SpaceMenuSection::Help,
459 SpaceMenuSection::Help => SpaceMenuSection::Actions,
460 };
461 self.cursor = 0;
462 }
463
464 fn item_count(&self) -> usize {
465 match self.section {
466 SpaceMenuSection::Actions => SPACE_ACTIONS.len(),
467 SpaceMenuSection::Help => HELP_TOPICS.len(),
468 }
469 }
470}
471
472pub const SPACE_ACTIONS: &[(&str, &str, &str)] = &[
474 ("spc+1", "transport", "focus transport controls"),
475 ("spc+2", "tracks", "focus the tracks panel"),
476 ("spc+3", "clip view", "focus clip / piano roll panel"),
477 ("spc+p", "play/pause","toggle transport playback"),
478 ("spc+r", "record", "toggle global recording"),
479 ("spc+l", "loop", "edit loop region"),
480 ("spc+m", "metronome", "toggle click track"),
481 ("spc+!", "panic", "kill all sound immediately"),
482 ("spc+a", "add instr", "add instrument track"),
483 ("spc+s", "save", "save project"),
484 ("spc+o", "open", "open project"),
485 ("spc+d", "delete", "delete selected track/clip"),
486 ("spc+e", "edit mode", "note-level piano roll editing"),
487 ("spc+q", "quantize", "snap notes to grid"),
488 ("spc+w", "presets", "save / load instrument presets"),
489 ("spc+v", "vibe", "cycle color theme"),
490 ("spc+h", "help", "open help topics"),
491];
492
493use super::clip_view::GridResolution;
496
497#[derive(Debug)]
498pub struct QuantizeModal {
499 pub open: bool,
500 pub grid: GridResolution,
501 pub strength: u8,
502 pub cursor: usize,
503}
504
505impl Default for QuantizeModal {
506 fn default() -> Self { Self::new() }
507}
508
509impl QuantizeModal {
510 pub fn new() -> Self {
511 Self { open: false, grid: GridResolution::Eighth, strength: 100, cursor: 0 }
512 }
513 pub fn open_with(&mut self, grid: GridResolution) {
514 self.open = true;
515 self.grid = grid;
516 self.strength = 100;
517 self.cursor = 0;
518 }
519 pub fn close(&mut self) { self.open = false; }
520 pub fn move_up(&mut self) { if self.cursor > 0 { self.cursor -= 1; } }
521 pub fn move_down(&mut self) { if self.cursor < 2 { self.cursor += 1; } }
522 pub fn adjust(&mut self, direction: i32) {
523 match self.cursor {
524 0 => { if direction > 0 { self.grid = self.grid.next(); } else { self.grid = self.grid.prev(); } }
525 1 => { self.strength = (self.strength as i32 + direction * 25).clamp(25, 100) as u8; }
526 _ => {}
527 }
528 }
529}
530
531pub const HELP_TOPICS: &[(&str, &str)] = &[
533 ("navigation", "moving between tracks, clips, and panes"),
534 ("transport", "play, pause, stop, record, loop, BPM"),
535 ("tracks", "mute, solo, arm, fx, volume, routing"),
536 ("clips", "selecting, jumping, clip-level fx"),
537 ("piano roll", "editing MIDI notes, velocity, quantize"),
538 ("fx & mixing", "adding effects, sends, master bus"),
539 ("shortcuts", "full keyboard shortcut reference"),
540 ("plugins", "loading and managing plugins"),
541];