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 ToggleRecord,
294 ToggleLoop,
295 ToggleMetronome,
296 Panic,
297 Save,
298 Open,
299 AddInstrument,
300 Delete,
301 CycleTheme,
302 NewTrack,
303 EditMode,
304 Quantize,
305 Presets,
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub enum ConfirmKind {
312 DeleteTrack,
313 DeleteClip,
314 DeletePreset,
315 OverwritePreset,
317}
318
319#[derive(Debug)]
320pub struct ConfirmModal {
321 pub open: bool,
322 pub kind: ConfirmKind,
323 pub message: String,
324}
325
326impl Default for ConfirmModal {
327 fn default() -> Self { Self::new() }
328}
329
330impl ConfirmModal {
331 pub fn new() -> Self {
332 Self { open: false, kind: ConfirmKind::DeleteTrack, message: String::new() }
333 }
334
335 pub fn show(&mut self, kind: ConfirmKind, message: &str) {
336 self.open = true;
337 self.kind = kind;
338 self.message = message.to_string();
339 }
340
341 pub fn close(&mut self) {
342 self.open = false;
343 self.message.clear();
344 }
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq)]
350pub enum InputModalKind {
351 SaveAs,
352 Open,
353 PresetName,
355}
356
357#[derive(Debug)]
358pub struct InputModal {
359 pub open: bool,
360 pub kind: InputModalKind,
361 pub buffer: String,
362 pub cursor: usize,
363}
364
365impl Default for InputModal {
366 fn default() -> Self { Self::new() }
367}
368
369impl InputModal {
370 pub fn new() -> Self {
371 Self { open: false, kind: InputModalKind::SaveAs, buffer: String::new(), cursor: 0 }
372 }
373
374 pub fn open_save(&mut self, default_name: &str) {
384 self.open = true;
385 self.kind = InputModalKind::SaveAs;
386 self.buffer = format!("{}{default_name}", crate::paths::session_prompt_dir());
387 self.cursor = self.buffer.len();
388 }
389
390 pub fn open_load(&mut self) {
395 self.open = true;
396 self.kind = InputModalKind::Open;
397 self.buffer = crate::paths::session_prompt_dir();
398 self.cursor = self.buffer.len();
399 }
400
401 pub fn open_preset_name(&mut self) {
405 self.open = true;
406 self.kind = InputModalKind::PresetName;
407 self.buffer.clear();
408 self.cursor = 0;
409 }
410
411 pub fn type_char(&mut self, ch: char) {
412 self.buffer.insert(self.cursor, ch);
413 self.cursor += 1;
414 }
415
416 pub fn backspace(&mut self) {
417 if self.cursor > 0 {
418 self.cursor -= 1;
419 self.buffer.remove(self.cursor);
420 }
421 }
422
423 pub fn delete(&mut self) {
424 if self.cursor < self.buffer.len() {
425 self.buffer.remove(self.cursor);
426 }
427 }
428
429 pub fn move_left(&mut self) {
430 if self.cursor > 0 { self.cursor -= 1; }
431 }
432
433 pub fn move_right(&mut self) {
434 if self.cursor < self.buffer.len() { self.cursor += 1; }
435 }
436
437 pub fn move_home(&mut self) {
438 self.cursor = 0;
439 }
440
441 pub fn move_end(&mut self) {
442 self.cursor = self.buffer.len();
443 }
444
445 pub fn close(&mut self) {
446 self.open = false;
447 self.buffer.clear();
448 self.cursor = 0;
449 }
450
451 pub fn value(&self) -> &str {
452 &self.buffer
453 }
454}
455
456#[derive(Debug)]
459pub struct SpaceMenu {
460 pub open: bool,
461 pub cursor: usize,
462 pub section: SpaceMenuSection,
464}
465
466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
467pub enum SpaceMenuSection {
468 Actions,
470 Help,
472}
473
474impl Default for SpaceMenu {
475 fn default() -> Self { Self::new() }
476}
477
478impl SpaceMenu {
479 pub fn new() -> Self {
480 Self { open: false, cursor: 0, section: SpaceMenuSection::Actions }
481 }
482
483 pub fn toggle(&mut self) {
484 self.open = !self.open;
485 if self.open { self.cursor = 0; self.section = SpaceMenuSection::Actions; }
486 }
487
488 pub fn move_up(&mut self) {
489 if self.cursor > 0 { self.cursor -= 1; }
490 }
491
492 pub fn move_down(&mut self) {
493 let max = self.item_count();
494 if self.cursor + 1 < max { self.cursor += 1; }
495 }
496
497 pub fn switch_section(&mut self) {
498 self.section = match self.section {
499 SpaceMenuSection::Actions => SpaceMenuSection::Help,
500 SpaceMenuSection::Help => SpaceMenuSection::Actions,
501 };
502 self.cursor = 0;
503 }
504
505 fn item_count(&self) -> usize {
506 match self.section {
507 SpaceMenuSection::Actions => SPACE_ACTIONS.len(),
508 SpaceMenuSection::Help => HELP_TOPICS.len(),
509 }
510 }
511}
512
513pub const SPACE_ACTIONS: &[(&str, &str, &str)] = &[
515 ("spc+1", "transport", "focus transport controls"),
516 ("spc+2", "tracks", "focus the tracks panel"),
517 ("spc+3", "clip view", "focus clip / piano roll panel"),
518 ("spc+p", "play/pause","toggle transport playback"),
519 ("spc+r", "record", "toggle global recording"),
520 ("spc+l", "loop", "edit loop region"),
521 ("spc+m", "metronome", "toggle click track"),
522 ("spc+!", "panic", "kill all sound immediately"),
523 ("spc+a", "add instr", "add instrument track"),
524 ("spc+s", "save", "save project"),
525 ("spc+o", "open", "open project"),
526 ("spc+d", "delete", "delete selected track/clip"),
527 ("spc+e", "edit mode", "note-level piano roll editing"),
528 ("spc+q", "quantize", "snap notes to grid"),
529 ("spc+w", "presets", "save / load instrument presets"),
530 ("spc+v", "vibe", "cycle color theme"),
531 ("spc+h", "help", "open help topics"),
532];
533
534use super::clip_view::GridResolution;
537
538#[derive(Debug)]
539pub struct QuantizeModal {
540 pub open: bool,
541 pub grid: GridResolution,
542 pub strength: u8,
543 pub cursor: usize,
544}
545
546impl Default for QuantizeModal {
547 fn default() -> Self { Self::new() }
548}
549
550impl QuantizeModal {
551 pub fn new() -> Self {
552 Self { open: false, grid: GridResolution::Eighth, strength: 100, cursor: 0 }
553 }
554 pub fn open_with(&mut self, grid: GridResolution) {
555 self.open = true;
556 self.grid = grid;
557 self.strength = 100;
558 self.cursor = 0;
559 }
560 pub fn close(&mut self) { self.open = false; }
561 pub fn move_up(&mut self) { if self.cursor > 0 { self.cursor -= 1; } }
562 pub fn move_down(&mut self) { if self.cursor < 2 { self.cursor += 1; } }
563 pub fn adjust(&mut self, direction: i32) {
564 match self.cursor {
565 0 => { if direction > 0 { self.grid = self.grid.next(); } else { self.grid = self.grid.prev(); } }
566 1 => { self.strength = (self.strength as i32 + direction * 25).clamp(25, 100) as u8; }
567 _ => {}
568 }
569 }
570}
571
572pub const HELP_TOPICS: &[(&str, &str)] = &[
574 ("navigation", "moving between tracks, clips, and panes"),
575 ("transport", "play, pause, stop, record, loop, BPM"),
576 ("tracks", "mute, solo, arm, fx, volume, routing"),
577 ("clips", "selecting, jumping, clip-level fx"),
578 ("piano roll", "editing MIDI notes, velocity, quantize"),
579 ("fx & mixing", "adding effects, sends, master bus"),
580 ("shortcuts", "full keyboard shortcut reference"),
581 ("plugins", "loading and managing plugins"),
582];