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