1use sim_kernel::{Expr, Symbol};
8use sim_lib_scene::{data_map, node, sym};
9
10pub const ENVELOPE_CURVE_VIEW_ID: &str = "view:component-envelope-curve";
12pub const ALGORITHM_ROUTING_VIEW_ID: &str = "view:component-algorithm-routing";
14pub const WIRING_DIAGRAM_VIEW_ID: &str = "view:component-wiring-diagram";
16pub const FILTER_RESPONSE_VIEW_ID: &str = "view:component-filter-response";
18pub const RESONATOR_RESPONSE_VIEW_ID: &str = "view:component-resonator-response";
20pub const FIXED_FILTER_BANK_VIEW_ID: &str = "view:component-fixed-filter-bank";
22pub const SEQUENCER_STEP_GRID_VIEW_ID: &str = "view:component-sequencer-step-grid";
24pub const POLYPHONY_ACTIVITY_VIEW_ID: &str = "view:component-polyphony-activity";
26pub const SCOPE_SPECTRUM_VIEW_ID: &str = "view:component-scope-spectrum";
28pub const SYSEX_COMPARISON_VIEW_ID: &str = "view:component-sysex-comparison";
30
31pub const SPECIALIZED_COMPONENT_VIEW_IDS: [&str; 10] = [
33 ENVELOPE_CURVE_VIEW_ID,
34 ALGORITHM_ROUTING_VIEW_ID,
35 WIRING_DIAGRAM_VIEW_ID,
36 FILTER_RESPONSE_VIEW_ID,
37 RESONATOR_RESPONSE_VIEW_ID,
38 FIXED_FILTER_BANK_VIEW_ID,
39 SEQUENCER_STEP_GRID_VIEW_ID,
40 POLYPHONY_ACTIVITY_VIEW_ID,
41 SCOPE_SPECTRUM_VIEW_ID,
42 SYSEX_COMPARISON_VIEW_ID,
43];
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub struct SpecializedComponentView {
48 pub view_id: &'static str,
50 pub component: &'static str,
52}
53
54pub const SPECIALIZED_DECLARING_COMPONENTS: [SpecializedComponentView; 12] = [
56 SpecializedComponentView {
57 view_id: ENVELOPE_CURVE_VIEW_ID,
58 component: "dx7-operator-eg",
59 },
60 SpecializedComponentView {
61 view_id: ENVELOPE_CURVE_VIEW_ID,
62 component: "r700-envelope",
63 },
64 SpecializedComponentView {
65 view_id: ALGORITHM_ROUTING_VIEW_ID,
66 component: "dx7-voice",
67 },
68 SpecializedComponentView {
69 view_id: WIRING_DIAGRAM_VIEW_ID,
70 component: "dx7-voice",
71 },
72 SpecializedComponentView {
73 view_id: FILTER_RESPONSE_VIEW_ID,
74 component: "r700-vcf",
75 },
76 SpecializedComponentView {
77 view_id: FILTER_RESPONSE_VIEW_ID,
78 component: "m55-ladder-filter",
79 },
80 SpecializedComponentView {
81 view_id: RESONATOR_RESPONSE_VIEW_ID,
82 component: "ps3300-resonator",
83 },
84 SpecializedComponentView {
85 view_id: FIXED_FILTER_BANK_VIEW_ID,
86 component: "m55-fixed-filter-bank",
87 },
88 SpecializedComponentView {
89 view_id: SEQUENCER_STEP_GRID_VIEW_ID,
90 component: "r700-sequencer",
91 },
92 SpecializedComponentView {
93 view_id: POLYPHONY_ACTIVITY_VIEW_ID,
94 component: "ps3300-poly-array",
95 },
96 SpecializedComponentView {
97 view_id: SCOPE_SPECTRUM_VIEW_ID,
98 component: "scope-spectrum-monitor",
99 },
100 SpecializedComponentView {
101 view_id: SYSEX_COMPARISON_VIEW_ID,
102 component: "dx7-sysex-patch",
103 },
104];
105
106pub const ENVELOPE_CURVE_FIXTURE: &str = "envelope-curve";
108pub const ALGORITHM_ROUTING_FIXTURE: &str = "algorithm-routing";
110pub const FILTER_RESPONSE_FIXTURE: &str = "filter-response";
112pub const RESONATOR_RESPONSE_FIXTURE: &str = "resonator-response";
114pub const FIXED_FILTER_BANK_FIXTURE: &str = "fixed-filter-bank";
116pub const SEQUENCER_STEP_GRID_FIXTURE: &str = "sequencer-step-grid";
118pub const POLYPHONY_ACTIVITY_FIXTURE: &str = "polyphony-activity";
120pub const SCOPE_SPECTRUM_FIXTURE: &str = "scope-spectrum";
122pub const SYSEX_COMPARISON_FIXTURE: &str = "sysex-comparison";
124
125const SPECIALIZED_FIXTURE_NAMES: [&str; 9] = [
126 ENVELOPE_CURVE_FIXTURE,
127 ALGORITHM_ROUTING_FIXTURE,
128 FILTER_RESPONSE_FIXTURE,
129 RESONATOR_RESPONSE_FIXTURE,
130 FIXED_FILTER_BANK_FIXTURE,
131 SEQUENCER_STEP_GRID_FIXTURE,
132 POLYPHONY_ACTIVITY_FIXTURE,
133 SCOPE_SPECTRUM_FIXTURE,
134 SYSEX_COMPARISON_FIXTURE,
135];
136
137pub fn specialized_view_ids() -> &'static [&'static str] {
139 &SPECIALIZED_COMPONENT_VIEW_IDS
140}
141
142pub fn specialized_declaring_components() -> &'static [SpecializedComponentView] {
144 &SPECIALIZED_DECLARING_COMPONENTS
145}
146
147pub fn specialized_fixture_names() -> [&'static str; 9] {
149 SPECIALIZED_FIXTURE_NAMES
150}
151
152pub fn specialized_snapshot(name: &str) -> Option<Expr> {
154 match name {
155 ENVELOPE_CURVE_FIXTURE => Some(envelope_curve_view()),
156 ALGORITHM_ROUTING_FIXTURE => Some(algorithm_routing_view()),
157 FILTER_RESPONSE_FIXTURE => Some(filter_response_view()),
158 RESONATOR_RESPONSE_FIXTURE => Some(resonator_response_view()),
159 FIXED_FILTER_BANK_FIXTURE => Some(fixed_filter_bank_view()),
160 SEQUENCER_STEP_GRID_FIXTURE => Some(sequencer_step_grid_view()),
161 POLYPHONY_ACTIVITY_FIXTURE => Some(polyphony_activity_view()),
162 SCOPE_SPECTRUM_FIXTURE => Some(scope_spectrum_view()),
163 SYSEX_COMPARISON_FIXTURE => Some(sysex_comparison_view()),
164 _ => None,
165 }
166}
167
168fn envelope_curve_view() -> Expr {
169 let curve = vec![
170 (0.0, 0.0),
171 (0.04, 1.0),
172 (0.18, 0.72),
173 (0.82, 0.48),
174 (1.0, 0.0),
175 ];
176 view_root(
177 ENVELOPE_CURVE_VIEW_ID,
178 "envelope-curve-editor",
179 vec![
180 response_plot(
181 ENVELOPE_CURVE_VIEW_ID,
182 "envelope-curve",
183 &[("level", curve)],
184 ),
185 stage_table(&[
186 ("attack", 0.04, 1.0),
187 ("decay", 0.14, 0.72),
188 ("sustain", 0.64, 0.48),
189 ("release", 0.18, 0.0),
190 ]),
191 node(
192 "button",
193 vec![
194 ("role", sym("envelope-point-editor")),
195 ("action", sym("edit-envelope")),
196 ("label", Expr::String("Edit point".to_owned())),
197 ],
198 ),
199 ],
200 )
201}
202
203fn algorithm_routing_view() -> Expr {
204 let routing = node(
205 "matrix",
206 vec![
207 ("lens", id(ALGORITHM_ROUTING_VIEW_ID)),
208 ("role", sym("algorithm-routing-matrix")),
209 (
210 "rows",
211 numeric_rows(&[
212 vec![0.0, 0.0, 0.8, 0.0, 0.0, 1.0],
213 vec![0.0, 0.0, 0.0, 0.5, 1.0, 0.0],
214 vec![0.0, 0.7, 0.0, 1.0, 0.0, 0.0],
215 vec![0.6, 0.0, 0.0, 0.0, 0.0, 0.0],
216 vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
217 vec![1.0, 0.0, 0.0, 0.0, 0.0, 0.0],
218 ]),
219 ),
220 (
221 "row-labels",
222 string_list(&["op1", "op2", "op3", "op4", "op5", "op6"]),
223 ),
224 (
225 "col-labels",
226 string_list(&["op1", "op2", "op3", "op4", "op5", "out"]),
227 ),
228 ("editable", Expr::Bool(true)),
229 ],
230 );
231 let wiring = node(
232 "graph",
233 vec![
234 ("lens", id(WIRING_DIAGRAM_VIEW_ID)),
235 ("role", sym("wiring-diagram")),
236 (
237 "nodes",
238 Expr::List(
239 ["op1", "op2", "op3", "op4", "op5", "op6", "out"]
240 .iter()
241 .map(|name| graph_node(name))
242 .collect(),
243 ),
244 ),
245 (
246 "edges",
247 Expr::List(vec![
248 graph_edge("op6", "op1"),
249 graph_edge("op5", "op2"),
250 graph_edge("op4", "op3"),
251 graph_edge("op3", "out"),
252 graph_edge("op1", "out"),
253 ]),
254 ),
255 ],
256 );
257 view_root(
258 ALGORITHM_ROUTING_VIEW_ID,
259 "algorithm-routing-view",
260 vec![routing, wiring],
261 )
262}
263
264fn filter_response_view() -> Expr {
265 view_root(
266 FILTER_RESPONSE_VIEW_ID,
267 "filter-response-view",
268 vec![
269 sweep_slider("cutoff", 80.0, 12_000.0, 1_800.0),
270 sweep_slider("resonance", 0.0, 1.0, 0.62),
271 response_plot(
272 FILTER_RESPONSE_VIEW_ID,
273 "filter-response-plot",
274 &[
275 (
276 "low-pass",
277 vec![
278 (20.0, -0.2),
279 (100.0, -0.1),
280 (1_000.0, -1.5),
281 (3_000.0, -8.0),
282 (10_000.0, -32.0),
283 ],
284 ),
285 (
286 "resonant",
287 vec![
288 (20.0, -0.4),
289 (100.0, -0.2),
290 (1_000.0, 2.4),
291 (3_000.0, -5.5),
292 (10_000.0, -30.0),
293 ],
294 ),
295 ],
296 ),
297 ],
298 )
299}
300
301fn resonator_response_view() -> Expr {
302 view_root(
303 RESONATOR_RESPONSE_VIEW_ID,
304 "resonator-response-view",
305 vec![
306 sweep_slider("frequency", 110.0, 7_040.0, 880.0),
307 sweep_slider("depth", 0.0, 1.0, 0.48),
308 response_plot(
309 RESONATOR_RESPONSE_VIEW_ID,
310 "resonator-response-plot",
311 &[
312 (
313 "bank-a",
314 vec![
315 (110.0, -12.0),
316 (220.0, -4.0),
317 (440.0, 0.0),
318 (880.0, 3.2),
319 (1_760.0, -5.0),
320 ],
321 ),
322 (
323 "bank-b",
324 vec![
325 (110.0, -16.0),
326 (220.0, -7.0),
327 (440.0, -1.0),
328 (880.0, 2.5),
329 (1_760.0, 0.2),
330 ],
331 ),
332 ],
333 ),
334 ],
335 )
336}
337
338fn fixed_filter_bank_view() -> Expr {
339 view_root(
340 FIXED_FILTER_BANK_VIEW_ID,
341 "fixed-filter-bank-view",
342 vec![
343 response_plot(
344 FIXED_FILTER_BANK_VIEW_ID,
345 "fixed-filter-bank-plot",
346 &[(
347 "band-gain",
348 vec![
349 (50.0, -3.0),
350 (120.0, 2.0),
351 (400.0, -5.0),
352 (1_200.0, 4.0),
353 (3_600.0, -1.0),
354 ],
355 )],
356 ),
357 node(
358 "table",
359 vec![
360 ("role", sym("fixed-filter-bank-bands")),
361 (
362 "rows",
363 Expr::List(vec![
364 band_row("50hz", 50.0, -3.0),
365 band_row("120hz", 120.0, 2.0),
366 band_row("400hz", 400.0, -5.0),
367 band_row("1k2", 1_200.0, 4.0),
368 band_row("3k6", 3_600.0, -1.0),
369 ]),
370 ),
371 ],
372 ),
373 ],
374 )
375}
376
377fn sequencer_step_grid_view() -> Expr {
378 view_root(
379 SEQUENCER_STEP_GRID_VIEW_ID,
380 "sequencer-step-grid",
381 vec![node(
382 "grid",
383 vec![
384 ("role", sym("sequencer-step-grid")),
385 (
386 "rows",
387 numeric_rows(&[
388 vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0],
389 vec![0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0],
390 vec![0.0, 0.0, 0.7, 0.0, 0.0, 0.0, 0.4, 0.0],
391 ]),
392 ),
393 ("row-labels", string_list(&["gate", "accent", "cv"])),
394 (
395 "col-labels",
396 string_list(&["1", "2", "3", "4", "5", "6", "7", "8"]),
397 ),
398 ("editable", Expr::Bool(true)),
399 ],
400 )],
401 )
402}
403
404fn polyphony_activity_view() -> Expr {
405 view_root(
406 POLYPHONY_ACTIVITY_VIEW_ID,
407 "polyphony-activity-view",
408 vec![node(
409 "grid",
410 vec![
411 ("role", sym("polyphony-activity-map")),
412 (
413 "rows",
414 numeric_rows(&[
415 vec![1.0, 0.8, 0.4, 0.0],
416 vec![0.6, 1.0, 0.7, 0.2],
417 vec![0.2, 0.5, 1.0, 0.9],
418 vec![0.0, 0.2, 0.6, 1.0],
419 ]),
420 ),
421 (
422 "row-labels",
423 string_list(&["cell-1", "cell-2", "cell-3", "cell-4"]),
424 ),
425 ("col-labels", string_list(&["vco", "vcf", "vca", "eg"])),
426 ],
427 )],
428 )
429}
430
431fn scope_spectrum_view() -> Expr {
432 view_root(
433 SCOPE_SPECTRUM_VIEW_ID,
434 "scope-spectrum-monitor",
435 vec![
436 node(
437 "waveform",
438 vec![
439 ("role", sym("scope-monitor")),
440 (
441 "samples",
442 number_list(&[0.0, 0.32, 0.74, 0.96, 0.62, 0.0, -0.58, -0.92, -0.31]),
443 ),
444 ],
445 ),
446 node(
447 "spectrum",
448 vec![
449 ("role", sym("spectrum-monitor")),
450 (
451 "bins",
452 number_list(&[0.0, -5.0, -12.0, -18.0, -26.0, -40.0]),
453 ),
454 ],
455 ),
456 ],
457 )
458}
459
460fn sysex_comparison_view() -> Expr {
461 view_root(
462 SYSEX_COMPARISON_VIEW_ID,
463 "sysex-comparison-view",
464 vec![
465 node(
466 "table",
467 vec![
468 ("role", sym("sysex-format-comparison")),
469 (
470 "rows",
471 Expr::List(vec![
472 sysex_row("hex", "f0 43 00 09 20 00 7f f7"),
473 sysex_row("binary", "11110000 01000011 00000000 00001001"),
474 sysex_row("lisp", "(dx7-patch :algorithm 5 :feedback 7)"),
475 ]),
476 ),
477 ],
478 ),
479 node(
480 "badge",
481 vec![
482 ("role", sym("round-trip-probe")),
483 ("status", sym("ok")),
484 (
485 "label",
486 Expr::String("lossless codec:lisp -> codec:binary".to_owned()),
487 ),
488 ],
489 ),
490 ],
491 )
492}
493
494fn view_root(lens: &str, role: &str, children: Vec<Expr>) -> Expr {
495 node(
496 "stack",
497 vec![
498 ("lens", id(lens)),
499 ("role", sym(role)),
500 ("dir", sym("column")),
501 ("declaring-components", declaring_components(lens)),
502 ("children", Expr::List(children)),
503 ],
504 )
505}
506
507fn declaring_components(view_id: &str) -> Expr {
508 Expr::List(
509 SPECIALIZED_DECLARING_COMPONENTS
510 .iter()
511 .filter(|entry| entry.view_id == view_id)
512 .map(|entry| qsym("audio-synth/component", entry.component))
513 .collect(),
514 )
515}
516
517fn stage_table(stages: &[(&str, f64, f64)]) -> Expr {
518 node(
519 "table",
520 vec![
521 ("role", sym("envelope-stage-table")),
522 (
523 "rows",
524 Expr::List(
525 stages
526 .iter()
527 .map(|(stage, time, level)| {
528 data_map(vec![
529 ("stage", sym(stage)),
530 ("time", number(*time)),
531 ("level", number(*level)),
532 ])
533 })
534 .collect(),
535 ),
536 ),
537 ],
538 )
539}
540
541fn response_plot(lens: &str, role: &str, series: &[(&str, Vec<(f64, f64)>)]) -> Expr {
542 let (min_x, max_x, min_y, max_y) = bounds(series);
543 node(
544 "plot",
545 vec![
546 ("lens", id(lens)),
547 ("role", sym(role)),
548 (
549 "axes",
550 data_map(vec![("x", axis(min_x, max_x)), ("y", axis(min_y, max_y))]),
551 ),
552 (
553 "series",
554 Expr::List(
555 series
556 .iter()
557 .map(|(name, points)| {
558 data_map(vec![
559 ("name", sym(name)),
560 ("style", sym("line")),
561 (
562 "points",
563 Expr::List(
564 points
565 .iter()
566 .map(|(x, y)| {
567 data_map(vec![("x", number(*x)), ("y", number(*y))])
568 })
569 .collect(),
570 ),
571 ),
572 ])
573 })
574 .collect(),
575 ),
576 ),
577 ],
578 )
579}
580
581fn bounds(series: &[(&str, Vec<(f64, f64)>)]) -> (f64, f64, f64, f64) {
582 let mut bounds: Option<(f64, f64, f64, f64)> = None;
583 for (_, points) in series {
584 for (x, y) in points {
585 bounds = Some(match bounds {
586 Some((min_x, max_x, min_y, max_y)) => {
587 (min_x.min(*x), max_x.max(*x), min_y.min(*y), max_y.max(*y))
588 }
589 None => (*x, *x, *y, *y),
590 });
591 }
592 }
593 bounds.unwrap_or((0.0, 1.0, 0.0, 1.0))
594}
595
596fn axis(min: f64, max: f64) -> Expr {
597 data_map(vec![("min", number(min)), ("max", number(max))])
598}
599
600fn sweep_slider(param: &str, min: f64, max: f64, value: f64) -> Expr {
601 node(
602 "slider",
603 vec![
604 ("role", sym("response-sweep")),
605 ("param", sym(param)),
606 ("min", number(min)),
607 ("max", number(max)),
608 ("value", number(value)),
609 ],
610 )
611}
612
613fn band_row(name: &str, frequency: f64, gain: f64) -> Expr {
614 data_map(vec![
615 ("name", Expr::String(name.to_owned())),
616 ("frequency", number(frequency)),
617 ("gain", number(gain)),
618 ])
619}
620
621fn sysex_row(format: &str, value: &str) -> Expr {
622 data_map(vec![
623 ("format", sym(format)),
624 ("value", Expr::String(value.to_owned())),
625 ])
626}
627
628fn graph_node(name: &str) -> Expr {
629 node(
630 "node",
631 vec![("id", sym(name)), ("label", Expr::String(name.to_owned()))],
632 )
633}
634
635fn graph_edge(from: &str, to: &str) -> Expr {
636 node("edge", vec![("from", sym(from)), ("to", sym(to))])
637}
638
639fn numeric_rows(rows: &[Vec<f64>]) -> Expr {
640 Expr::List(
641 rows.iter()
642 .map(|row| Expr::List(row.iter().map(|value| number(*value)).collect()))
643 .collect(),
644 )
645}
646
647fn number_list(values: &[f64]) -> Expr {
648 Expr::List(values.iter().map(|value| number(*value)).collect())
649}
650
651fn string_list(values: &[&str]) -> Expr {
652 Expr::List(
653 values
654 .iter()
655 .map(|value| Expr::String((*value).to_owned()))
656 .collect(),
657 )
658}
659
660fn id(name: &str) -> Expr {
661 Expr::Symbol(Symbol::new(name))
662}
663
664fn qsym(namespace: &str, name: &str) -> Expr {
665 Expr::Symbol(Symbol::qualified(namespace, name))
666}
667
668use sim_value::build::float as number;