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