1use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum Editor {
12 Pattern,
14 Graph,
16}
17
18impl Editor {
19 pub fn tab_label(self) -> &'static str {
21 match self {
22 Editor::Pattern => "SEQ",
23 Editor::Graph => "GRAPH",
24 }
25 }
26
27 pub fn title(self) -> &'static str {
29 match self {
30 Editor::Pattern => "Sequencer",
31 Editor::Graph => "Graph",
32 }
33 }
34
35 pub fn intent(self) -> &'static str {
37 match self {
38 Editor::Pattern => "time · voices",
39 Editor::Graph => "signal · routing",
40 }
41 }
42
43 pub fn next(self) -> Self {
44 match self {
45 Editor::Pattern => Editor::Graph,
46 Editor::Graph => Editor::Pattern,
47 }
48 }
49
50 pub const ALL: [Editor; 2] = [Editor::Pattern, Editor::Graph];
51}
52
53#[derive(Debug, Clone, Copy)]
55pub struct InputContext<'a> {
56 pub editor: Editor,
57 pub mode: &'a Mode,
58 pub graph_is_nested: bool,
60 pub help_open: bool,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum BottomPane {
66 Waveform,
67 Spectrum,
68}
69
70impl BottomPane {
71 pub fn next(self) -> Self {
72 match self {
73 BottomPane::Waveform => BottomPane::Spectrum,
74 BottomPane::Spectrum => BottomPane::Waveform,
75 }
76 }
77
78 pub fn label(self) -> &'static str {
79 match self {
80 BottomPane::Waveform => "SCOPE",
81 BottomPane::Spectrum => "SPECTRUM",
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum Mode {
89 Normal,
90 Edit,
91}
92
93impl Mode {
94 pub fn label(self) -> &'static str {
95 match self {
96 Mode::Normal => "NAV",
97 Mode::Edit => "EDIT",
98 }
99 }
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum Action {
105 Quit,
106 CycleEditor,
108 ToggleEdit,
110 TogglePlay,
111 MoveUp,
112 MoveDown,
113 MoveLeft,
114 MoveRight,
115 NoteInput(i32),
116 DeleteNote,
117 OctaveUp,
118 OctaveDown,
119 BpmUp,
120 BpmDown,
121 ParamFineUp,
122 ParamFineDown,
123 EuclideanFill,
124 RandomizeVoice,
125 ReverseVoice,
126 ShiftVoiceLeft,
127 ShiftVoiceRight,
128 VelocityUp,
129 VelocityDown,
130 GateCycle,
131 Undo,
132 Redo,
133 SwingUp,
134 SwingDown,
135 SaveProject,
136 LoadProject,
137 CycleBottomPane,
138 EnterGraph,
139 ExitGraph,
140 ToggleHelp,
142}
143
144pub fn handle_key(key: KeyEvent, ctx: &InputContext<'_>) -> Option<Action> {
146 if key.kind == KeyEventKind::Release {
147 return None;
148 }
149
150 if ctx.help_open {
151 return match key.code {
152 KeyCode::Esc | KeyCode::Char('?') => Some(Action::ToggleHelp),
153 _ => None,
154 };
155 }
156
157 if key.modifiers.contains(KeyModifiers::CONTROL) {
158 return match key.code {
159 KeyCode::Char('c') | KeyCode::Char('q') => Some(Action::Quit),
160 KeyCode::Char('s') => Some(Action::SaveProject),
161 KeyCode::Char('o') => Some(Action::LoadProject),
162 KeyCode::Char('z') => Some(Action::Undo),
163 KeyCode::Char('y') => Some(Action::Redo),
164 _ => None,
165 };
166 }
167
168 if key.modifiers.contains(KeyModifiers::SHIFT) {
169 match key.code {
170 KeyCode::Left => return Some(Action::ParamFineDown),
171 KeyCode::Right => return Some(Action::ParamFineUp),
172 KeyCode::Char('U') => return Some(Action::Redo),
173 _ => {}
174 }
175 }
176
177 match key.code {
178 KeyCode::Tab => return Some(Action::CycleEditor),
179 KeyCode::Char('?') => return Some(Action::ToggleHelp),
180 KeyCode::Char(' ') => return Some(Action::TogglePlay),
181 KeyCode::Up => return Some(Action::MoveUp),
182 KeyCode::Down => return Some(Action::MoveDown),
183 KeyCode::Left => return Some(Action::MoveLeft),
184 KeyCode::Right => return Some(Action::MoveRight),
185 KeyCode::Char('+') | KeyCode::Char('=') => return Some(Action::BpmUp),
186 KeyCode::Char('-') => return Some(Action::BpmDown),
187 KeyCode::Char('[') => return Some(Action::OctaveDown),
188 KeyCode::Char(']') => return Some(Action::OctaveUp),
189 KeyCode::Char('{') => return Some(Action::SwingDown),
190 KeyCode::Char('}') => return Some(Action::SwingUp),
191 KeyCode::Char('`') => return Some(Action::CycleBottomPane),
192 KeyCode::Esc if *ctx.mode == Mode::Edit => return Some(Action::ToggleEdit),
193 KeyCode::Esc
194 if *ctx.mode == Mode::Normal && ctx.editor == Editor::Graph && ctx.graph_is_nested =>
195 {
196 return Some(Action::ExitGraph);
197 }
198 _ => {}
199 }
200
201 match ctx.editor {
202 Editor::Pattern => pattern_keys(key.code, ctx.mode),
203 Editor::Graph => graph_keys(key.code, ctx.mode),
204 }
205}
206
207fn pattern_keys(code: KeyCode, mode: &Mode) -> Option<Action> {
208 match mode {
209 Mode::Normal => match code {
210 KeyCode::Char('q') => Some(Action::Quit),
211 KeyCode::Char('e') => Some(Action::ToggleEdit),
212 KeyCode::Char('u') => Some(Action::Undo),
213 KeyCode::Char('h') => Some(Action::MoveLeft),
214 KeyCode::Char('l') => Some(Action::MoveRight),
215 KeyCode::Char('k') => Some(Action::MoveUp),
216 KeyCode::Char('j') => Some(Action::MoveDown),
217 _ => None,
218 },
219 Mode::Edit => match code {
220 KeyCode::Delete | KeyCode::Backspace => Some(Action::DeleteNote),
221 KeyCode::Char('z') => Some(Action::NoteInput(0)),
222 KeyCode::Char('s') => Some(Action::NoteInput(1)),
223 KeyCode::Char('x') => Some(Action::NoteInput(2)),
224 KeyCode::Char('d') => Some(Action::NoteInput(3)),
225 KeyCode::Char('c') => Some(Action::NoteInput(4)),
226 KeyCode::Char('v') => Some(Action::NoteInput(5)),
227 KeyCode::Char('g') => Some(Action::NoteInput(6)),
228 KeyCode::Char('b') => Some(Action::NoteInput(7)),
229 KeyCode::Char('h') => Some(Action::NoteInput(8)),
230 KeyCode::Char('n') => Some(Action::NoteInput(9)),
231 KeyCode::Char('j') => Some(Action::NoteInput(10)),
232 KeyCode::Char('m') => Some(Action::NoteInput(11)),
233 KeyCode::Char(ch @ '0'..='9') => Some(Action::NoteInput(ch as i32 - '0' as i32)),
234 KeyCode::Char('f') => Some(Action::EuclideanFill),
235 KeyCode::Char('r') => Some(Action::RandomizeVoice),
236 KeyCode::Char('t') => Some(Action::ReverseVoice),
237 KeyCode::Char(',') => Some(Action::ShiftVoiceLeft),
238 KeyCode::Char('.') => Some(Action::ShiftVoiceRight),
239 KeyCode::Char('w') => Some(Action::VelocityUp),
240 KeyCode::Char('q') => Some(Action::VelocityDown),
241 KeyCode::Char('a') => Some(Action::GateCycle),
242 _ => None,
243 },
244 }
245}
246
247fn graph_keys(code: KeyCode, mode: &Mode) -> Option<Action> {
248 match mode {
249 Mode::Normal => match code {
250 KeyCode::Char('q') => Some(Action::Quit),
251 KeyCode::Char('e') => Some(Action::ToggleEdit),
252 KeyCode::Char('u') => Some(Action::Undo),
253 KeyCode::Char('h') => Some(Action::MoveLeft),
254 KeyCode::Char('l') => Some(Action::MoveRight),
255 KeyCode::Char('k') => Some(Action::MoveUp),
256 KeyCode::Char('j') => Some(Action::MoveDown),
257 KeyCode::Enter => Some(Action::EnterGraph),
258 _ => None,
259 },
260 Mode::Edit => None,
261 }
262}