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 Rhodes,
108 Sampler,
109 LittlePhatty,
110}
111
112impl InstrumentType {
113 pub fn label(self) -> &'static str {
114 match self {
115 Self::Synth => "Phosphor Synth",
116 Self::DrumRack => "Drum Rack",
117 Self::DX7 => "DX7",
118 Self::Jupiter8 => "Jupiter-8",
119 Self::Odyssey => "Odyssey",
120 Self::Juno60 => "Juno-60",
121 Self::Rhodes => "Rhodes",
122 Self::Sampler => "Sampler",
123 Self::LittlePhatty => "Little Phatty",
124 }
125 }
126
127 pub fn description(self) -> &'static str {
128 match self {
129 Self::Synth => "polyphonic subtractive synthesizer",
130 Self::DrumRack => "drum machine with sample pads",
131 Self::DX7 => "6-operator FM synthesizer",
132 Self::Jupiter8 => "dual-VCO analog poly synthesizer",
133 Self::Odyssey => "duophonic synth with 3 filter types",
134 Self::Juno60 => "single-DCO poly with BBD chorus",
135 Self::Rhodes => "modelled tine electric piano",
136 Self::Sampler => "sample-based instrument",
137 Self::LittlePhatty => "monophonic Moog with morphing waves",
138 }
139 }
140
141 pub const ALL: &[InstrumentType] = &[Self::Synth, Self::DrumRack, Self::DX7, Self::Jupiter8, Self::Odyssey, Self::Juno60, Self::Rhodes, Self::Sampler, Self::LittlePhatty];
145}
146
147#[derive(Debug)]
148pub struct InstrumentModal {
149 pub open: bool,
150 pub cursor: usize,
151}
152
153impl Default for InstrumentModal {
154 fn default() -> Self { Self::new() }
155}
156
157impl InstrumentModal {
158 pub fn new() -> Self {
159 Self { open: false, cursor: 0 }
160 }
161
162 pub fn move_up(&mut self) {
163 if self.cursor > 0 { self.cursor -= 1; }
164 }
165
166 pub fn move_down(&mut self) {
167 if self.cursor + 1 < InstrumentType::ALL.len() { self.cursor += 1; }
168 }
169
170 pub fn selected(&self) -> InstrumentType {
171 InstrumentType::ALL[self.cursor]
172 }
173}
174
175#[derive(Debug)]
184pub struct PresetModal {
185 pub open: bool,
186 pub instrument: Option<InstrumentType>,
188 pub track_idx: usize,
191 pub cursor: usize,
192 pub entries: Vec<String>,
194 pub error: Option<String>,
196 pub pending_name: String,
198}
199
200impl Default for PresetModal {
201 fn default() -> Self { Self::new() }
202}
203
204impl PresetModal {
205 pub const SAVE_ROW: usize = 0;
207
208 pub fn new() -> Self {
209 Self {
210 open: false,
211 instrument: None,
212 track_idx: 0,
213 cursor: 0,
214 entries: Vec::new(),
215 error: None,
216 pending_name: String::new(),
217 }
218 }
219
220 pub fn show(&mut self, instrument: InstrumentType, track_idx: usize, entries: Vec<String>) {
221 self.open = true;
222 self.instrument = Some(instrument);
223 self.track_idx = track_idx;
224 self.cursor = 0;
225 self.entries = entries;
226 self.error = None;
227 self.pending_name.clear();
228 }
229
230 pub fn close(&mut self) {
231 self.open = false;
232 self.entries.clear();
233 self.error = None;
234 self.pending_name.clear();
235 self.cursor = 0;
236 }
237
238 pub fn item_count(&self) -> usize { self.entries.len() + 1 }
240
241 pub fn move_up(&mut self) {
242 if self.cursor > 0 { self.cursor -= 1; }
243 }
244
245 pub fn move_down(&mut self) {
246 if self.cursor + 1 < self.item_count() { self.cursor += 1; }
247 }
248
249 pub fn selected_preset(&self) -> Option<usize> {
251 self.cursor.checked_sub(1).filter(|i| *i < self.entries.len())
252 }
253
254 pub fn selected_name(&self) -> Option<&str> {
256 self.selected_preset().map(|i| self.entries[i].as_str())
257 }
258
259 pub fn set_entries(&mut self, entries: Vec<String>) {
262 self.entries = entries;
263 let max = self.item_count() - 1;
264 if self.cursor > max { self.cursor = max; }
265 }
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
272pub enum SpaceAction {
273 PlayPause,
274 ToggleRecord,
275 ToggleLoop,
276 ToggleMetronome,
277 Panic,
278 Save,
279 Open,
280 AddInstrument,
281 Delete,
282 CycleTheme,
283 NewTrack,
284 EditMode,
285 Quantize,
286 Presets,
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum ConfirmKind {
293 DeleteTrack,
294 DeleteClip,
295 DeletePreset,
296 OverwritePreset,
298}
299
300#[derive(Debug)]
301pub struct ConfirmModal {
302 pub open: bool,
303 pub kind: ConfirmKind,
304 pub message: String,
305}
306
307impl Default for ConfirmModal {
308 fn default() -> Self { Self::new() }
309}
310
311impl ConfirmModal {
312 pub fn new() -> Self {
313 Self { open: false, kind: ConfirmKind::DeleteTrack, message: String::new() }
314 }
315
316 pub fn show(&mut self, kind: ConfirmKind, message: &str) {
317 self.open = true;
318 self.kind = kind;
319 self.message = message.to_string();
320 }
321
322 pub fn close(&mut self) {
323 self.open = false;
324 self.message.clear();
325 }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq)]
331pub enum InputModalKind {
332 SaveAs,
333 Open,
334 PresetName,
336}
337
338#[derive(Debug)]
339pub struct InputModal {
340 pub open: bool,
341 pub kind: InputModalKind,
342 pub buffer: String,
343 pub cursor: usize,
344}
345
346impl Default for InputModal {
347 fn default() -> Self { Self::new() }
348}
349
350impl InputModal {
351 pub fn new() -> Self {
352 Self { open: false, kind: InputModalKind::SaveAs, buffer: String::new(), cursor: 0 }
353 }
354
355 pub fn open_save(&mut self, default_name: &str) {
365 self.open = true;
366 self.kind = InputModalKind::SaveAs;
367 self.buffer = format!("{}{default_name}", crate::paths::session_prompt_dir());
368 self.cursor = self.buffer.len();
369 }
370
371 pub fn open_load(&mut self) {
376 self.open = true;
377 self.kind = InputModalKind::Open;
378 self.buffer = crate::paths::session_prompt_dir();
379 self.cursor = self.buffer.len();
380 }
381
382 pub fn open_preset_name(&mut self) {
386 self.open = true;
387 self.kind = InputModalKind::PresetName;
388 self.buffer.clear();
389 self.cursor = 0;
390 }
391
392 pub fn type_char(&mut self, ch: char) {
393 self.buffer.insert(self.cursor, ch);
394 self.cursor += 1;
395 }
396
397 pub fn backspace(&mut self) {
398 if self.cursor > 0 {
399 self.cursor -= 1;
400 self.buffer.remove(self.cursor);
401 }
402 }
403
404 pub fn delete(&mut self) {
405 if self.cursor < self.buffer.len() {
406 self.buffer.remove(self.cursor);
407 }
408 }
409
410 pub fn move_left(&mut self) {
411 if self.cursor > 0 { self.cursor -= 1; }
412 }
413
414 pub fn move_right(&mut self) {
415 if self.cursor < self.buffer.len() { self.cursor += 1; }
416 }
417
418 pub fn move_home(&mut self) {
419 self.cursor = 0;
420 }
421
422 pub fn move_end(&mut self) {
423 self.cursor = self.buffer.len();
424 }
425
426 pub fn close(&mut self) {
427 self.open = false;
428 self.buffer.clear();
429 self.cursor = 0;
430 }
431
432 pub fn value(&self) -> &str {
433 &self.buffer
434 }
435}
436
437#[derive(Debug)]
440pub struct SpaceMenu {
441 pub open: bool,
442 pub cursor: usize,
443 pub section: SpaceMenuSection,
445}
446
447#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448pub enum SpaceMenuSection {
449 Actions,
451 Help,
453}
454
455impl Default for SpaceMenu {
456 fn default() -> Self { Self::new() }
457}
458
459impl SpaceMenu {
460 pub fn new() -> Self {
461 Self { open: false, cursor: 0, section: SpaceMenuSection::Actions }
462 }
463
464 pub fn toggle(&mut self) {
465 self.open = !self.open;
466 if self.open { self.cursor = 0; self.section = SpaceMenuSection::Actions; }
467 }
468
469 pub fn move_up(&mut self) {
470 if self.cursor > 0 { self.cursor -= 1; }
471 }
472
473 pub fn move_down(&mut self) {
474 let max = self.item_count();
475 if self.cursor + 1 < max { self.cursor += 1; }
476 }
477
478 pub fn switch_section(&mut self) {
479 self.section = match self.section {
480 SpaceMenuSection::Actions => SpaceMenuSection::Help,
481 SpaceMenuSection::Help => SpaceMenuSection::Actions,
482 };
483 self.cursor = 0;
484 }
485
486 fn item_count(&self) -> usize {
487 match self.section {
488 SpaceMenuSection::Actions => SPACE_ACTIONS.len(),
489 SpaceMenuSection::Help => HELP_TOPICS.len(),
490 }
491 }
492}
493
494pub const SPACE_ACTIONS: &[(&str, &str, &str)] = &[
496 ("spc+1", "transport", "focus transport controls"),
497 ("spc+2", "tracks", "focus the tracks panel"),
498 ("spc+3", "clip view", "focus clip / piano roll panel"),
499 ("spc+p", "play/pause","toggle transport playback"),
500 ("spc+r", "record", "toggle global recording"),
501 ("spc+l", "loop", "edit loop region"),
502 ("spc+m", "metronome", "toggle click track"),
503 ("spc+!", "panic", "kill all sound immediately"),
504 ("spc+a", "add instr", "add instrument track"),
505 ("spc+s", "save", "save project"),
506 ("spc+o", "open", "open project"),
507 ("spc+d", "delete", "delete selected track/clip"),
508 ("spc+e", "edit mode", "note-level piano roll editing"),
509 ("spc+q", "quantize", "snap notes to grid"),
510 ("spc+w", "presets", "save / load instrument presets"),
511 ("spc+v", "vibe", "cycle color theme"),
512 ("spc+h", "help", "open help topics"),
513];
514
515use super::clip_view::GridResolution;
518
519#[derive(Debug)]
520pub struct QuantizeModal {
521 pub open: bool,
522 pub grid: GridResolution,
523 pub strength: u8,
524 pub cursor: usize,
525}
526
527impl Default for QuantizeModal {
528 fn default() -> Self { Self::new() }
529}
530
531impl QuantizeModal {
532 pub fn new() -> Self {
533 Self { open: false, grid: GridResolution::Eighth, strength: 100, cursor: 0 }
534 }
535 pub fn open_with(&mut self, grid: GridResolution) {
536 self.open = true;
537 self.grid = grid;
538 self.strength = 100;
539 self.cursor = 0;
540 }
541 pub fn close(&mut self) { self.open = false; }
542 pub fn move_up(&mut self) { if self.cursor > 0 { self.cursor -= 1; } }
543 pub fn move_down(&mut self) { if self.cursor < 2 { self.cursor += 1; } }
544 pub fn adjust(&mut self, direction: i32) {
545 match self.cursor {
546 0 => { if direction > 0 { self.grid = self.grid.next(); } else { self.grid = self.grid.prev(); } }
547 1 => { self.strength = (self.strength as i32 + direction * 25).clamp(25, 100) as u8; }
548 _ => {}
549 }
550 }
551}
552
553pub const HELP_TOPICS: &[(&str, &str)] = &[
555 ("navigation", "moving between tracks, clips, and panes"),
556 ("transport", "play, pause, stop, record, loop, BPM"),
557 ("tracks", "mute, solo, arm, fx, volume, routing"),
558 ("clips", "selecting, jumping, clip-level fx"),
559 ("piano roll", "editing MIDI notes, velocity, quantize"),
560 ("fx & mixing", "adding effects, sends, master bus"),
561 ("shortcuts", "full keyboard shortcut reference"),
562 ("plugins", "loading and managing plugins"),
563];