1use sim_kernel::{Error, Expr, Result, Symbol};
2
3use crate::arranger::{
4 Arranger, ArrangerPlacement, FilterRef, PitchRemap, PlacementTransform, PlayableRef,
5 StretchPolicy, TracePolicy,
6};
7use crate::arranger_music_expr::{music_from_expr, music_to_expr};
8use crate::{LaneId, LaneTarget, Pitch, PitchClass, Time};
9
10const NS: &str = "music/arranger";
11
12impl Arranger {
13 pub fn to_expr(&self) -> Expr {
15 map(vec![
16 ("tag", tag_expr("arranger")),
17 (
18 "lanes",
19 Expr::Vector(
20 self.lanes
21 .iter()
22 .map(|lane| Expr::String(lane.0.clone()))
23 .collect(),
24 ),
25 ),
26 (
27 "placements",
28 Expr::Vector(
29 self.placements
30 .iter()
31 .map(ArrangerPlacement::to_expr)
32 .collect(),
33 ),
34 ),
35 ])
36 }
37
38 pub fn from_expr(expr: &Expr) -> Result<Self> {
40 let entries = expr_map(expr, "arranger")?;
41 expect_tag(entries, "arranger", "arranger")?;
42 let lanes = optional_vector(entries, "lanes")?
43 .iter()
44 .map(|expr| Ok(LaneId::new(expr_string(expr, "lane")?.to_owned())))
45 .collect::<Result<Vec<_>>>()?;
46 let placements = expr_vector(lookup_required(entries, "placements")?, "placements")?
47 .iter()
48 .map(ArrangerPlacement::from_expr)
49 .collect::<Result<Vec<_>>>()?;
50 Self::new(placements, lanes)
51 }
52}
53
54impl ArrangerPlacement {
55 pub fn to_expr(&self) -> Expr {
57 map(vec![
58 ("tag", tag_expr("placement")),
59 ("id", Expr::Symbol(self.id.clone())),
60 ("playable", self.playable.to_expr()),
61 ("at", time_expr(self.at)),
62 (
63 "duration",
64 self.duration.map(time_expr).unwrap_or(Expr::Nil),
65 ),
66 ("lane", Expr::String(self.lane.0.clone())),
67 (
68 "targets",
69 Expr::Vector(self.targets.iter().map(lane_target_expr).collect()),
70 ),
71 ("stretch", self.stretch.to_expr()),
72 (
73 "transform",
74 Expr::Vector(
75 self.transform
76 .iter()
77 .map(PlacementTransform::to_expr)
78 .collect(),
79 ),
80 ),
81 ("remap-pitch", self.remap_pitch.to_expr()),
82 (
83 "filter",
84 self.filter
85 .as_ref()
86 .map(FilterRef::to_expr)
87 .unwrap_or(Expr::Nil),
88 ),
89 (
90 "seed",
91 self.seed
92 .map(|seed| Expr::String(seed.to_string()))
93 .unwrap_or(Expr::Nil),
94 ),
95 ("trace", Expr::Symbol(self.trace.symbol())),
96 ])
97 }
98
99 pub fn from_expr(expr: &Expr) -> Result<Self> {
101 let entries = expr_map(expr, "arranger placement")?;
102 expect_tag(entries, "placement", "arranger placement")?;
103 let duration = match lookup_required(entries, "duration")? {
104 Expr::Nil => None,
105 expr => Some(time_from_expr(expr)?),
106 };
107 let placement = Self {
108 id: expr_symbol(lookup_required(entries, "id")?, "placement id")?,
109 playable: PlayableRef::from_expr(lookup_required(entries, "playable")?)?,
110 at: time_from_expr(lookup_required(entries, "at")?)?,
111 duration,
112 lane: LaneId::new(expr_string(lookup_required(entries, "lane")?, "lane")?.to_owned()),
113 targets: expr_vector(lookup_required(entries, "targets")?, "placement targets")?
114 .iter()
115 .map(lane_target_from_expr)
116 .collect::<Result<Vec<_>>>()?,
117 stretch: StretchPolicy::from_expr(lookup_required(entries, "stretch")?)?,
118 transform: expr_vector(lookup_required(entries, "transform")?, "transform")?
119 .iter()
120 .map(PlacementTransform::from_expr)
121 .collect::<Result<Vec<_>>>()?,
122 remap_pitch: PitchRemap::from_expr(lookup_required(entries, "remap-pitch")?)?,
123 filter: match lookup_required(entries, "filter")? {
124 Expr::Nil => None,
125 expr => Some(FilterRef::from_expr(expr)?),
126 },
127 seed: match lookup_required(entries, "seed")? {
128 Expr::Nil => None,
129 expr => Some(expr_u64(expr, "placement seed")?),
130 },
131 trace: TracePolicy::from_expr(lookup_required(entries, "trace")?)?,
132 };
133 placement.validate()?;
134 Ok(placement)
135 }
136}
137
138impl PlayableRef {
139 pub fn to_expr(&self) -> Expr {
141 match self {
142 Self::Inline(music) => map(vec![
143 ("tag", tag_expr("playable-ref")),
144 ("kind", tag_expr("inline")),
145 ("value", music_to_expr(music)),
146 ]),
147 Self::Symbol(symbol) => map(vec![
148 ("tag", tag_expr("playable-ref")),
149 ("kind", tag_expr("symbol")),
150 ("value", Expr::Symbol(symbol.clone())),
151 ]),
152 }
153 }
154
155 pub fn from_expr(expr: &Expr) -> Result<Self> {
157 let entries = expr_map(expr, "playable ref")?;
158 expect_tag(entries, "playable-ref", "playable ref")?;
159 match symbol_name(lookup_required(entries, "kind")?, "playable ref kind")? {
160 "inline" => Ok(Self::inline(music_from_expr(lookup_required(
161 entries, "value",
162 )?)?)),
163 "symbol" => Ok(Self::symbol(expr_symbol(
164 lookup_required(entries, "value")?,
165 "playable symbol",
166 )?)),
167 _ => Err(Error::Eval("playable ref kind is invalid".to_owned())),
168 }
169 }
170}
171
172impl FilterRef {
173 pub fn to_expr(&self) -> Expr {
175 map(vec![
176 ("tag", tag_expr("filter")),
177 ("id", Expr::Symbol(self.id.clone())),
178 (
179 "keep-lanes",
180 Expr::Vector(
181 self.keep_lanes
182 .iter()
183 .map(|lane| Expr::String(lane.0.clone()))
184 .collect(),
185 ),
186 ),
187 ])
188 }
189
190 pub fn from_expr(expr: &Expr) -> Result<Self> {
192 let entries = expr_map(expr, "filter")?;
193 expect_tag(entries, "filter", "filter")?;
194 Ok(Self {
195 id: expr_symbol(lookup_required(entries, "id")?, "filter id")?,
196 keep_lanes: expr_vector(lookup_required(entries, "keep-lanes")?, "keep lanes")?
197 .iter()
198 .map(|expr| Ok(LaneId::new(expr_string(expr, "keep lane")?.to_owned())))
199 .collect::<Result<Vec<_>>>()?,
200 })
201 }
202}
203
204impl StretchPolicy {
205 fn to_expr(&self) -> Expr {
206 match self {
207 Self::None => map(vec![
208 ("tag", tag_expr("stretch")),
209 ("kind", tag_expr("none")),
210 ]),
211 Self::TempoRatio(ratio) => map(vec![
212 ("tag", tag_expr("stretch")),
213 ("kind", tag_expr("tempo-ratio")),
214 ("value", time_expr(*ratio)),
215 ]),
216 Self::TimeRatio(ratio) => map(vec![
217 ("tag", tag_expr("stretch")),
218 ("kind", tag_expr("time-ratio")),
219 ("value", time_expr(*ratio)),
220 ]),
221 Self::FitToDuration => map(vec![
222 ("tag", tag_expr("stretch")),
223 ("kind", tag_expr("fit")),
224 ]),
225 }
226 }
227
228 fn from_expr(expr: &Expr) -> Result<Self> {
229 let entries = expr_map(expr, "stretch policy")?;
230 expect_tag(entries, "stretch", "stretch policy")?;
231 match symbol_name(lookup_required(entries, "kind")?, "stretch kind")? {
232 "none" => Ok(Self::None),
233 "tempo-ratio" => Ok(Self::TempoRatio(time_from_expr(lookup_required(
234 entries, "value",
235 )?)?)),
236 "time-ratio" => Ok(Self::TimeRatio(time_from_expr(lookup_required(
237 entries, "value",
238 )?)?)),
239 "fit" => Ok(Self::FitToDuration),
240 _ => Err(Error::Eval("stretch policy kind is invalid".to_owned())),
241 }
242 }
243}
244
245impl PlacementTransform {
246 fn to_expr(&self) -> Expr {
247 match self {
248 Self::TransposeSemitones(semitones) => {
249 value_transform("transpose-semitones", *semitones)
250 }
251 Self::TransposeOctaves(octaves) => value_transform("transpose-octaves", *octaves),
252 Self::InvertAroundPitch(axis) => map(vec![
253 ("tag", tag_expr("transform")),
254 ("kind", tag_expr("invert-pitch")),
255 ("value", pitch_expr(*axis)),
256 ]),
257 Self::InvertAroundPitchClass(axis) => {
258 value_transform("invert-pitch-class", axis.value())
259 }
260 Self::Retrograde => map(vec![
261 ("tag", tag_expr("transform")),
262 ("kind", tag_expr("retrograde")),
263 ]),
264 }
265 }
266
267 fn from_expr(expr: &Expr) -> Result<Self> {
268 let entries = expr_map(expr, "placement transform")?;
269 expect_tag(entries, "transform", "placement transform")?;
270 match symbol_name(lookup_required(entries, "kind")?, "transform kind")? {
271 "transpose-semitones" => Ok(Self::TransposeSemitones(expr_i32(
272 lookup_required(entries, "value")?,
273 "transpose semitones",
274 )?)),
275 "transpose-octaves" => Ok(Self::TransposeOctaves(expr_i16(
276 lookup_required(entries, "value")?,
277 "transpose octaves",
278 )?)),
279 "invert-pitch" => Ok(Self::InvertAroundPitch(pitch_from_expr(lookup_required(
280 entries, "value",
281 )?)?)),
282 "invert-pitch-class" => Ok(Self::InvertAroundPitchClass(
283 PitchClass::new(expr_u8(lookup_required(entries, "value")?, "pitch class")?)
284 .map_err(|_| Error::Eval("pitch class is invalid".to_owned()))?,
285 )),
286 "retrograde" => Ok(Self::Retrograde),
287 _ => Err(Error::Eval(
288 "placement transform kind is invalid".to_owned(),
289 )),
290 }
291 }
292}
293
294impl PitchRemap {
295 fn to_expr(&self) -> Expr {
296 match self {
297 Self::None => map(vec![
298 ("tag", tag_expr("pitch-remap")),
299 ("kind", tag_expr("none")),
300 ]),
301 Self::Chromatic(semitones) => value_remap("chromatic", *semitones),
302 Self::PitchClass { from, to } => map(vec![
303 ("tag", tag_expr("pitch-remap")),
304 ("kind", tag_expr("pitch-class")),
305 ("from", Expr::String(from.value().to_string())),
306 ("to", Expr::String(to.value().to_string())),
307 ]),
308 Self::DrumKey(items) => map(vec![
309 ("tag", tag_expr("pitch-remap")),
310 ("kind", tag_expr("drum-key")),
311 (
312 "items",
313 Expr::Vector(items.iter().map(drum_key_expr).collect()),
314 ),
315 ]),
316 Self::ScaleDegree(symbol) => symbolic_remap_expr("scale-degree", symbol),
317 Self::ChordTone(symbol) => symbolic_remap_expr("chord-tone", symbol),
318 Self::Tuning(symbol) => symbolic_remap_expr("tuning", symbol),
319 Self::Vector(symbol) => symbolic_remap_expr("vector", symbol),
320 Self::Matrix(symbol) => symbolic_remap_expr("matrix", symbol),
321 Self::Callable(symbol) => symbolic_remap_expr("callable", symbol),
322 }
323 }
324
325 fn from_expr(expr: &Expr) -> Result<Self> {
326 let entries = expr_map(expr, "pitch remap")?;
327 expect_tag(entries, "pitch-remap", "pitch remap")?;
328 match symbol_name(lookup_required(entries, "kind")?, "pitch remap kind")? {
329 "none" => Ok(Self::None),
330 "chromatic" => Ok(Self::Chromatic(expr_i32(
331 lookup_required(entries, "value")?,
332 "chromatic remap",
333 )?)),
334 "pitch-class" => Ok(Self::PitchClass {
335 from: PitchClass::new(expr_u8(lookup_required(entries, "from")?, "from")?)
336 .map_err(|_| Error::Eval("source pitch class is invalid".to_owned()))?,
337 to: PitchClass::new(expr_u8(lookup_required(entries, "to")?, "to")?)
338 .map_err(|_| Error::Eval("target pitch class is invalid".to_owned()))?,
339 }),
340 "drum-key" => expr_vector(lookup_required(entries, "items")?, "drum key items")?
341 .iter()
342 .map(drum_key_from_expr)
343 .collect::<Result<Vec<_>>>()
344 .map(Self::DrumKey),
345 "scale-degree" => symbolic_remap(entries, Self::ScaleDegree),
346 "chord-tone" => symbolic_remap(entries, Self::ChordTone),
347 "tuning" => symbolic_remap(entries, Self::Tuning),
348 "vector" => symbolic_remap(entries, Self::Vector),
349 "matrix" => symbolic_remap(entries, Self::Matrix),
350 "callable" => symbolic_remap(entries, Self::Callable),
351 _ => Err(Error::Eval("pitch remap kind is invalid".to_owned())),
352 }
353 }
354}
355
356impl TracePolicy {
357 pub fn symbol(self) -> Symbol {
359 match self {
360 Self::Off => tag("trace-off"),
361 Self::Diagnostics => tag("trace-diagnostics"),
362 Self::Full => tag("trace-full"),
363 }
364 }
365
366 fn from_expr(expr: &Expr) -> Result<Self> {
367 match symbol_name(expr, "trace policy")? {
368 "trace-off" => Ok(Self::Off),
369 "trace-diagnostics" => Ok(Self::Diagnostics),
370 "trace-full" => Ok(Self::Full),
371 _ => Err(Error::Eval("trace policy is invalid".to_owned())),
372 }
373 }
374}
375
376impl PartialEq for Arranger {
377 fn eq(&self, other: &Self) -> bool {
378 self.to_expr().canonical_eq(&other.to_expr())
379 }
380}
381
382impl Eq for Arranger {}
383
384impl PartialEq for ArrangerPlacement {
385 fn eq(&self, other: &Self) -> bool {
386 self.to_expr().canonical_eq(&other.to_expr())
387 }
388}
389
390impl Eq for ArrangerPlacement {}
391
392impl PartialEq for PlayableRef {
393 fn eq(&self, other: &Self) -> bool {
394 self.to_expr().canonical_eq(&other.to_expr())
395 }
396}
397
398impl Eq for PlayableRef {}
399
400fn time_expr(time: Time) -> Expr {
401 map(vec![
402 ("numer", Expr::String(time.numer().to_string())),
403 ("denom", Expr::String(time.denom().to_string())),
404 ])
405}
406
407fn time_from_expr(expr: &Expr) -> Result<Time> {
408 let entries = expr_map(expr, "time")?;
409 let denominator = expr_i64(lookup_required(entries, "denom")?, "time denominator")?;
410 if denominator == 0 {
411 return Err(Error::Eval("time denominator cannot be zero".to_owned()));
412 }
413 Ok(Time::new(
414 expr_i64(lookup_required(entries, "numer")?, "time numerator")?,
415 denominator,
416 ))
417}
418
419fn pitch_expr(pitch: Pitch) -> Expr {
420 Expr::String(
421 pitch
422 .to_midi()
423 .map(|midi| format!("midi:{midi}"))
424 .unwrap_or_else(|| format!("semitone:{}", pitch.semitone())),
425 )
426}
427
428fn pitch_from_expr(expr: &Expr) -> Result<Pitch> {
429 let value = expr_string(expr, "pitch")?;
430 if let Some(midi) = value.strip_prefix("midi:") {
431 return Ok(Pitch::from_midi(
432 midi.parse::<u8>()
433 .map_err(|_| Error::Eval("MIDI pitch is invalid".to_owned()))?,
434 ));
435 }
436 if let Some(semitone) = value.strip_prefix("semitone:") {
437 return Ok(Pitch::from_semitone(semitone.parse::<i32>().map_err(
438 |_| Error::Eval("semitone pitch is invalid".to_owned()),
439 )?));
440 }
441 crate::parse_pitch(value).map_err(|_| Error::Eval("pitch is invalid".to_owned()))
442}
443
444fn lane_target_expr(target: &LaneTarget) -> Expr {
445 match target {
446 LaneTarget::Instrument(symbol) => target_expr("instrument", symbol),
447 LaneTarget::Stream(symbol) => target_expr("stream", symbol),
448 LaneTarget::Control(symbol) => target_expr("control", symbol),
449 LaneTarget::None => map(vec![("kind", tag_expr("none"))]),
450 }
451}
452
453fn lane_target_from_expr(expr: &Expr) -> Result<LaneTarget> {
454 let entries = expr_map(expr, "lane target")?;
455 match symbol_name(lookup_required(entries, "kind")?, "lane target kind")? {
456 "instrument" => Ok(LaneTarget::Instrument(expr_symbol(
457 lookup_required(entries, "symbol")?,
458 "target symbol",
459 )?)),
460 "stream" => Ok(LaneTarget::Stream(expr_symbol(
461 lookup_required(entries, "symbol")?,
462 "target symbol",
463 )?)),
464 "control" => Ok(LaneTarget::Control(expr_symbol(
465 lookup_required(entries, "symbol")?,
466 "target symbol",
467 )?)),
468 "none" => Ok(LaneTarget::None),
469 _ => Err(Error::Eval("lane target kind is invalid".to_owned())),
470 }
471}
472
473fn value_transform<T: ToString>(kind: &'static str, value: T) -> Expr {
474 map(vec![
475 ("tag", tag_expr("transform")),
476 ("kind", tag_expr(kind)),
477 ("value", Expr::String(value.to_string())),
478 ])
479}
480
481fn value_remap<T: ToString>(kind: &'static str, value: T) -> Expr {
482 map(vec![
483 ("tag", tag_expr("pitch-remap")),
484 ("kind", tag_expr(kind)),
485 ("value", Expr::String(value.to_string())),
486 ])
487}
488
489fn drum_key_expr((from, to): &(u8, u8)) -> Expr {
490 map(vec![
491 ("from", Expr::String(from.to_string())),
492 ("to", Expr::String(to.to_string())),
493 ])
494}
495
496fn drum_key_from_expr(expr: &Expr) -> Result<(u8, u8)> {
497 let item = expr_map(expr, "drum key item")?;
498 Ok((
499 expr_u8(lookup_required(item, "from")?, "drum key from")?,
500 expr_u8(lookup_required(item, "to")?, "drum key to")?,
501 ))
502}
503
504fn symbolic_remap_expr(kind: &'static str, symbol: &Symbol) -> Expr {
505 map(vec![
506 ("tag", tag_expr("pitch-remap")),
507 ("kind", tag_expr(kind)),
508 ("symbol", Expr::Symbol(symbol.clone())),
509 ])
510}
511
512fn symbolic_remap(
513 entries: &[(Expr, Expr)],
514 build: impl FnOnce(Symbol) -> PitchRemap,
515) -> Result<PitchRemap> {
516 Ok(build(expr_symbol(
517 lookup_required(entries, "symbol")?,
518 "pitch remap symbol",
519 )?))
520}
521
522fn target_expr(kind: &'static str, symbol: &Symbol) -> Expr {
523 map(vec![
524 ("kind", tag_expr(kind)),
525 ("symbol", Expr::Symbol(symbol.clone())),
526 ])
527}
528
529fn map(entries: Vec<(&'static str, Expr)>) -> Expr {
530 Expr::Map(
531 entries
532 .into_iter()
533 .map(|(key, value)| (field(key), value))
534 .collect(),
535 )
536}
537
538fn field(name: &'static str) -> Expr {
539 sim_value::build::qsym(NS, name)
540}
541
542fn tag(name: &'static str) -> Symbol {
543 Symbol::qualified(NS, name)
544}
545
546fn tag_expr(name: &'static str) -> Expr {
547 Expr::Symbol(tag(name))
548}
549
550fn expr_map<'a>(expr: &'a Expr, context: &str) -> Result<&'a [(Expr, Expr)]> {
551 match expr {
552 Expr::Map(entries) => Ok(entries),
553 _ => Err(Error::Eval(format!("{context} must be a map"))),
554 }
555}
556
557fn expr_vector<'a>(expr: &'a Expr, context: &str) -> Result<&'a [Expr]> {
558 match expr {
559 Expr::Vector(items) => Ok(items),
560 _ => Err(Error::Eval(format!("{context} must be a vector"))),
561 }
562}
563
564fn optional_vector<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a [Expr]> {
565 match lookup(entries, name) {
566 Some(expr) => expr_vector(expr, name),
567 None => Ok(&[]),
568 }
569}
570
571fn lookup_required<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
572 lookup(entries, name).ok_or_else(|| Error::Eval(format!("arranger field is missing: {name}")))
573}
574
575fn lookup<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
576 entries.iter().find_map(|(key, value)| match key {
577 Expr::Symbol(symbol)
578 if symbol.namespace.as_deref() == Some(NS) && symbol.name.as_ref() == name =>
579 {
580 Some(value)
581 }
582 _ => None,
583 })
584}
585
586fn expect_tag(entries: &[(Expr, Expr)], name: &'static str, context: &str) -> Result<()> {
587 match lookup(entries, "tag") {
588 Some(Expr::Symbol(symbol))
589 if symbol.namespace.as_deref() == Some(NS) && symbol.name.as_ref() == name =>
590 {
591 Ok(())
592 }
593 Some(_) => Err(Error::Eval(format!("{context} tag is invalid"))),
594 None => Err(Error::Eval(format!("{context} tag is missing"))),
595 }
596}
597
598fn symbol_name<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
599 match expr {
600 Expr::Symbol(symbol) if symbol.namespace.as_deref() == Some(NS) => Ok(symbol.name.as_ref()),
601 _ => Err(Error::Eval(format!("{context} must be an arranger symbol"))),
602 }
603}
604
605fn expr_symbol(expr: &Expr, context: &str) -> Result<Symbol> {
606 match expr {
607 Expr::Symbol(symbol) => Ok(symbol.clone()),
608 _ => Err(Error::Eval(format!("{context} must be a symbol"))),
609 }
610}
611
612fn expr_string<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
613 match expr {
614 Expr::String(value) => Ok(value),
615 _ => Err(Error::Eval(format!("{context} must be a string"))),
616 }
617}
618
619fn expr_i16(expr: &Expr, context: &str) -> Result<i16> {
620 parse_number(expr, context)
621}
622
623fn expr_i32(expr: &Expr, context: &str) -> Result<i32> {
624 parse_number(expr, context)
625}
626
627fn expr_i64(expr: &Expr, context: &str) -> Result<i64> {
628 parse_number(expr, context)
629}
630
631fn expr_u8(expr: &Expr, context: &str) -> Result<u8> {
632 parse_number(expr, context)
633}
634
635fn expr_u64(expr: &Expr, context: &str) -> Result<u64> {
636 parse_number(expr, context)
637}
638
639fn parse_number<T>(expr: &Expr, context: &str) -> Result<T>
640where
641 T: std::str::FromStr,
642{
643 let text = match expr {
644 Expr::String(value) => value.as_str(),
645 Expr::Number(value) => value.canonical.as_str(),
646 _ => return Err(Error::Eval(format!("{context} must be a number"))),
647 };
648 text.parse()
649 .map_err(|_| Error::Eval(format!("{context} is invalid")))
650}