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