1use sim_kernel::{Expr, Symbol};
8use sim_lib_scene::{data_map, node, sym};
9use sim_value::build::{int, list, text, uint};
10
11pub const PERFORMANCE_KEYBOARD_VIEW_ID: &str = "view:performance-keyboard";
13
14pub const PERFORMANCE_KEYBOARD_DEMO_FIXTURE: &str = "player-chain-instrument";
16
17#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct PerformanceKeyboardBinding {
20 pub target: Symbol,
22 pub source: Symbol,
24 pub input: Symbol,
26 pub player_chain: Symbol,
28 pub instrument: Symbol,
30 pub stream: Symbol,
32 pub channel: u8,
34}
35
36impl PerformanceKeyboardBinding {
37 pub fn browser(player_chain: Symbol, instrument: Symbol) -> Self {
39 Self {
40 target: Symbol::qualified("music/performance-source", "keyboard"),
41 source: Symbol::qualified("music/performance-source", "keyboard"),
42 input: Symbol::qualified("midi/input", "keyboard"),
43 player_chain,
44 instrument,
45 stream: Symbol::qualified("stream/browser", "performance-keyboard"),
46 channel: 0,
47 }
48 }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct PerformanceKeyMap {
54 pub name: String,
56 pub editable: bool,
58 pub entries: Vec<PerformanceKeyMapEntry>,
60 pub velocity: u8,
62 pub transpose: i8,
64 pub scale_lock: bool,
66}
67
68impl Default for PerformanceKeyMap {
69 fn default() -> Self {
70 Self::qwerty_two_row()
71 }
72}
73
74impl PerformanceKeyMap {
75 pub fn qwerty_two_row() -> Self {
77 let mut entries = Vec::new();
78 push_degree_row(&mut entries, &LOWER_ROW, 0);
79 push_degree_row(&mut entries, &UPPER_ROW, 1);
80 entries.extend([
81 PerformanceKeyMapEntry::new("Space", " ", "Sustain", PerformanceKeyAction::Sustain),
82 PerformanceKeyMapEntry::new(
83 "BracketLeft",
84 "[",
85 "Octave down",
86 PerformanceKeyAction::OctaveShift { amount: -1 },
87 ),
88 PerformanceKeyMapEntry::new(
89 "BracketRight",
90 "]",
91 "Octave up",
92 PerformanceKeyAction::OctaveShift { amount: 1 },
93 ),
94 PerformanceKeyMapEntry::new(
95 "Comma",
96 ",",
97 "Transpose down",
98 PerformanceKeyAction::Transpose { amount: -1 },
99 ),
100 PerformanceKeyMapEntry::new(
101 "Period",
102 ".",
103 "Transpose up",
104 PerformanceKeyAction::Transpose { amount: 1 },
105 ),
106 PerformanceKeyMapEntry::new(
107 "Backslash",
108 "\\",
109 "Scale lock",
110 PerformanceKeyAction::ScaleLock,
111 ),
112 PerformanceKeyMapEntry::new("Escape", "Esc", "Panic", PerformanceKeyAction::Panic),
113 PerformanceKeyMapEntry::new(
114 "F1",
115 "F1",
116 "Velocity low",
117 PerformanceKeyAction::Velocity { value: 40 },
118 ),
119 PerformanceKeyMapEntry::new(
120 "F2",
121 "F2",
122 "Velocity medium",
123 PerformanceKeyAction::Velocity { value: 72 },
124 ),
125 PerformanceKeyMapEntry::new(
126 "F3",
127 "F3",
128 "Velocity strong",
129 PerformanceKeyAction::Velocity { value: 100 },
130 ),
131 PerformanceKeyMapEntry::new(
132 "F4",
133 "F4",
134 "Velocity full",
135 PerformanceKeyAction::Velocity { value: 127 },
136 ),
137 ]);
138 Self {
139 name: "qwerty-two-row".to_owned(),
140 editable: true,
141 entries,
142 velocity: 96,
143 transpose: 0,
144 scale_lock: false,
145 }
146 }
147}
148
149#[derive(Clone, Debug, PartialEq, Eq)]
151pub struct PerformanceKeyMapEntry {
152 pub code: String,
154 pub key: String,
156 pub label: String,
158 pub action: PerformanceKeyAction,
160}
161
162impl PerformanceKeyMapEntry {
163 pub fn new(
165 code: impl Into<String>,
166 key: impl Into<String>,
167 label: impl Into<String>,
168 action: PerformanceKeyAction,
169 ) -> Self {
170 Self {
171 code: code.into(),
172 key: key.into(),
173 label: label.into(),
174 action,
175 }
176 }
177}
178
179#[derive(Clone, Copy, Debug, PartialEq, Eq)]
181pub enum PerformanceKeyAction {
182 Degree {
184 degree: i8,
186 octave: i8,
188 },
189 Note {
191 midi: i32,
193 },
194 Sustain,
196 OctaveShift {
198 amount: i8,
200 },
201 Transpose {
203 amount: i8,
205 },
206 Panic,
208 ScaleLock,
210 Velocity {
212 value: u8,
214 },
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
219pub struct PerformanceKeyboardState {
220 pub base_midi: i32,
222 pub octaves: u8,
224 pub octave_shift: i8,
226 pub scale_lock: Vec<u8>,
228 pub held_notes: Vec<i32>,
230 pub generated_notes: Vec<i32>,
232 pub sustain: bool,
234 pub pitch_bend: u16,
236 pub key_map: PerformanceKeyMap,
238}
239
240impl Default for PerformanceKeyboardState {
241 fn default() -> Self {
242 Self {
243 base_midi: 48,
244 octaves: 3,
245 octave_shift: 0,
246 scale_lock: vec![0, 2, 4, 5, 7, 9, 11],
247 held_notes: Vec::new(),
248 generated_notes: Vec::new(),
249 sustain: false,
250 pitch_bend: 8192,
251 key_map: PerformanceKeyMap::default(),
252 }
253 }
254}
255
256pub fn performance_keyboard_view(
258 binding: &PerformanceKeyboardBinding,
259 state: &PerformanceKeyboardState,
260) -> Expr {
261 let shifted_base = state.base_midi + i32::from(state.octave_shift) * 12;
262 node(
263 "keyboard",
264 vec![
265 ("lens", sym(PERFORMANCE_KEYBOARD_VIEW_ID)),
266 ("role", sym("performance-keyboard")),
267 ("label", text("On-screen keyboard")),
268 ("target", Expr::Symbol(binding.target.clone())),
269 ("source", Expr::Symbol(binding.source.clone())),
270 ("input", Expr::Symbol(binding.input.clone())),
271 ("channel", uint(u64::from(binding.channel))),
272 ("base-midi", int(i64::from(shifted_base))),
273 ("octaves", uint(u64::from(state.octaves))),
274 ("octave-shift", int(i64::from(state.octave_shift))),
275 ("sustain", Expr::Bool(state.sustain)),
276 ("pitch-bend", uint(u64::from(state.pitch_bend))),
277 ("key-map", key_map_expr(&state.key_map)),
278 (
279 "scale-lock",
280 list(
281 state
282 .scale_lock
283 .iter()
284 .map(|note| uint(u64::from(*note)))
285 .collect(),
286 ),
287 ),
288 (
289 "held-notes",
290 list(
291 state
292 .held_notes
293 .iter()
294 .map(|note| int(i64::from(*note)))
295 .collect(),
296 ),
297 ),
298 (
299 "generated-notes",
300 list(
301 state
302 .generated_notes
303 .iter()
304 .map(|note| int(i64::from(*note)))
305 .collect(),
306 ),
307 ),
308 ("binding", binding_expr(binding)),
309 ("keys", list(keys(shifted_base, state))),
310 ],
311 )
312}
313
314pub fn performance_keyboard_demo_scene() -> Expr {
317 let binding = PerformanceKeyboardBinding::browser(
318 Symbol::qualified("music/player-chain", "onscreen-keyboard"),
319 Symbol::qualified("audio-synth/instrument", "dx7"),
320 );
321 let state = PerformanceKeyboardState {
322 held_notes: vec![60, 64],
323 generated_notes: vec![67, 72],
324 sustain: true,
325 ..PerformanceKeyboardState::default()
326 };
327 performance_keyboard_view(&binding, &state)
328}
329
330fn binding_expr(binding: &PerformanceKeyboardBinding) -> Expr {
331 data_map(vec![
332 ("target", Expr::Symbol(binding.target.clone())),
333 ("source", Expr::Symbol(binding.source.clone())),
334 ("input", Expr::Symbol(binding.input.clone())),
335 ("player-chain", Expr::Symbol(binding.player_chain.clone())),
336 ("instrument", Expr::Symbol(binding.instrument.clone())),
337 ("stream", Expr::Symbol(binding.stream.clone())),
338 ("channel", uint(u64::from(binding.channel))),
339 ])
340}
341
342fn key_map_expr(key_map: &PerformanceKeyMap) -> Expr {
343 data_map(vec![
344 ("name", text(key_map.name.clone())),
345 ("editable", Expr::Bool(key_map.editable)),
346 ("velocity", uint(u64::from(key_map.velocity))),
347 ("transpose", int(i64::from(key_map.transpose))),
348 ("scale-lock", Expr::Bool(key_map.scale_lock)),
349 (
350 "entries",
351 list(key_map.entries.iter().map(key_map_entry_expr).collect()),
352 ),
353 ])
354}
355
356fn key_map_entry_expr(entry: &PerformanceKeyMapEntry) -> Expr {
357 let mut fields = vec![
358 ("code", text(entry.code.clone())),
359 ("key", text(entry.key.clone())),
360 ("label", text(entry.label.clone())),
361 ];
362 match entry.action {
363 PerformanceKeyAction::Degree { degree, octave } => {
364 fields.extend([
365 ("action", text("degree")),
366 ("degree", int(i64::from(degree))),
367 ("octave", int(i64::from(octave))),
368 ]);
369 }
370 PerformanceKeyAction::Note { midi } => {
371 fields.extend([("action", text("note")), ("midi", int(i64::from(midi)))]);
372 }
373 PerformanceKeyAction::Sustain => fields.push(("action", text("sustain"))),
374 PerformanceKeyAction::OctaveShift { amount } => {
375 fields.extend([
376 ("action", text("octave-shift")),
377 ("amount", int(i64::from(amount))),
378 ]);
379 }
380 PerformanceKeyAction::Transpose { amount } => {
381 fields.extend([
382 ("action", text("transpose")),
383 ("amount", int(i64::from(amount))),
384 ]);
385 }
386 PerformanceKeyAction::Panic => fields.push(("action", text("panic"))),
387 PerformanceKeyAction::ScaleLock => fields.push(("action", text("scale-lock"))),
388 PerformanceKeyAction::Velocity { value } => {
389 fields.extend([
390 ("action", text("velocity")),
391 ("value", uint(u64::from(value))),
392 ]);
393 }
394 }
395 data_map(fields)
396}
397
398fn keys(base_midi: i32, state: &PerformanceKeyboardState) -> Vec<Expr> {
399 let count = usize::from(state.octaves) * 12;
400 (0..count)
401 .map(|offset| {
402 let midi = base_midi + offset as i32;
403 let class = midi.rem_euclid(12) as u8;
404 data_map(vec![
405 ("midi", int(i64::from(midi))),
406 ("label", text(note_label(midi))),
407 ("white", Expr::Bool(is_white_key(class))),
408 ("scale", Expr::Bool(state.scale_lock.contains(&class))),
409 ("held", Expr::Bool(state.held_notes.contains(&midi))),
410 (
411 "generated",
412 Expr::Bool(state.generated_notes.contains(&midi)),
413 ),
414 ])
415 })
416 .collect()
417}
418
419const LOWER_ROW: [(&str, &str, &str, i8); 12] = [
420 ("KeyZ", "z", "Z", 0),
421 ("KeyS", "s", "S", 1),
422 ("KeyX", "x", "X", 2),
423 ("KeyD", "d", "D", 3),
424 ("KeyC", "c", "C", 4),
425 ("KeyV", "v", "V", 5),
426 ("KeyG", "g", "G", 6),
427 ("KeyB", "b", "B", 7),
428 ("KeyH", "h", "H", 8),
429 ("KeyN", "n", "N", 9),
430 ("KeyJ", "j", "J", 10),
431 ("KeyM", "m", "M", 11),
432];
433
434const UPPER_ROW: [(&str, &str, &str, i8); 12] = [
435 ("KeyQ", "q", "Q", 0),
436 ("Digit2", "2", "2", 1),
437 ("KeyW", "w", "W", 2),
438 ("Digit3", "3", "3", 3),
439 ("KeyE", "e", "E", 4),
440 ("KeyR", "r", "R", 5),
441 ("Digit5", "5", "5", 6),
442 ("KeyT", "t", "T", 7),
443 ("Digit6", "6", "6", 8),
444 ("KeyY", "y", "Y", 9),
445 ("Digit7", "7", "7", 10),
446 ("KeyU", "u", "U", 11),
447];
448
449fn push_degree_row(
450 entries: &mut Vec<PerformanceKeyMapEntry>,
451 row: &[(&str, &str, &str, i8)],
452 octave: i8,
453) {
454 entries.extend(row.iter().map(|(code, key, label, degree)| {
455 PerformanceKeyMapEntry::new(
456 *code,
457 *key,
458 *label,
459 PerformanceKeyAction::Degree {
460 degree: *degree,
461 octave,
462 },
463 )
464 }));
465}
466
467fn is_white_key(class: u8) -> bool {
468 matches!(class, 0 | 2 | 4 | 5 | 7 | 9 | 11)
469}
470
471fn note_label(midi: i32) -> String {
472 const NAMES: [&str; 12] = [
473 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
474 ];
475 let class = midi.rem_euclid(12) as usize;
476 let octave = midi.div_euclid(12) - 1;
477 format!("{}{}", NAMES[class], octave)
478}