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) => value_transform("invert-pitch-class", axis.0),
258 Self::Retrograde => map(vec![
259 ("tag", tag_expr("transform")),
260 ("kind", tag_expr("retrograde")),
261 ]),
262 }
263 }
264
265 fn from_expr(expr: &Expr) -> Result<Self> {
266 let entries = expr_map(expr, "placement transform")?;
267 expect_tag(entries, "transform", "placement transform")?;
268 match symbol_name(lookup_required(entries, "kind")?, "transform kind")? {
269 "transpose-semitones" => Ok(Self::TransposeSemitones(expr_i32(
270 lookup_required(entries, "value")?,
271 "transpose semitones",
272 )?)),
273 "transpose-octaves" => Ok(Self::TransposeOctaves(expr_i16(
274 lookup_required(entries, "value")?,
275 "transpose octaves",
276 )?)),
277 "invert-pitch" => Ok(Self::InvertAroundPitch(pitch_from_expr(lookup_required(
278 entries, "value",
279 )?)?)),
280 "invert-pitch-class" => Ok(Self::InvertAroundPitchClass(
281 PitchClass::new(expr_u8(lookup_required(entries, "value")?, "pitch class")?)
282 .map_err(|_| Error::Eval("pitch class is invalid".to_owned()))?,
283 )),
284 "retrograde" => Ok(Self::Retrograde),
285 _ => Err(Error::Eval(
286 "placement transform kind is invalid".to_owned(),
287 )),
288 }
289 }
290}
291
292impl PitchRemap {
293 fn to_expr(&self) -> Expr {
294 match self {
295 Self::None => map(vec![
296 ("tag", tag_expr("pitch-remap")),
297 ("kind", tag_expr("none")),
298 ]),
299 Self::Chromatic(semitones) => value_remap("chromatic", *semitones),
300 Self::PitchClass { from, to } => map(vec![
301 ("tag", tag_expr("pitch-remap")),
302 ("kind", tag_expr("pitch-class")),
303 ("from", Expr::String(from.0.to_string())),
304 ("to", Expr::String(to.0.to_string())),
305 ]),
306 Self::DrumKey(items) => map(vec![
307 ("tag", tag_expr("pitch-remap")),
308 ("kind", tag_expr("drum-key")),
309 (
310 "items",
311 Expr::Vector(items.iter().map(drum_key_expr).collect()),
312 ),
313 ]),
314 Self::ScaleDegree(symbol) => symbolic_remap_expr("scale-degree", symbol),
315 Self::ChordTone(symbol) => symbolic_remap_expr("chord-tone", symbol),
316 Self::Tuning(symbol) => symbolic_remap_expr("tuning", symbol),
317 Self::Vector(symbol) => symbolic_remap_expr("vector", symbol),
318 Self::Matrix(symbol) => symbolic_remap_expr("matrix", symbol),
319 Self::Callable(symbol) => symbolic_remap_expr("callable", symbol),
320 }
321 }
322
323 fn from_expr(expr: &Expr) -> Result<Self> {
324 let entries = expr_map(expr, "pitch remap")?;
325 expect_tag(entries, "pitch-remap", "pitch remap")?;
326 match symbol_name(lookup_required(entries, "kind")?, "pitch remap kind")? {
327 "none" => Ok(Self::None),
328 "chromatic" => Ok(Self::Chromatic(expr_i32(
329 lookup_required(entries, "value")?,
330 "chromatic remap",
331 )?)),
332 "pitch-class" => Ok(Self::PitchClass {
333 from: PitchClass::new(expr_u8(lookup_required(entries, "from")?, "from")?)
334 .map_err(|_| Error::Eval("source pitch class is invalid".to_owned()))?,
335 to: PitchClass::new(expr_u8(lookup_required(entries, "to")?, "to")?)
336 .map_err(|_| Error::Eval("target pitch class is invalid".to_owned()))?,
337 }),
338 "drum-key" => expr_vector(lookup_required(entries, "items")?, "drum key items")?
339 .iter()
340 .map(drum_key_from_expr)
341 .collect::<Result<Vec<_>>>()
342 .map(Self::DrumKey),
343 "scale-degree" => symbolic_remap(entries, Self::ScaleDegree),
344 "chord-tone" => symbolic_remap(entries, Self::ChordTone),
345 "tuning" => symbolic_remap(entries, Self::Tuning),
346 "vector" => symbolic_remap(entries, Self::Vector),
347 "matrix" => symbolic_remap(entries, Self::Matrix),
348 "callable" => symbolic_remap(entries, Self::Callable),
349 _ => Err(Error::Eval("pitch remap kind is invalid".to_owned())),
350 }
351 }
352}
353
354impl TracePolicy {
355 pub fn symbol(self) -> Symbol {
357 match self {
358 Self::Off => tag("trace-off"),
359 Self::Diagnostics => tag("trace-diagnostics"),
360 Self::Full => tag("trace-full"),
361 }
362 }
363
364 fn from_expr(expr: &Expr) -> Result<Self> {
365 match symbol_name(expr, "trace policy")? {
366 "trace-off" => Ok(Self::Off),
367 "trace-diagnostics" => Ok(Self::Diagnostics),
368 "trace-full" => Ok(Self::Full),
369 _ => Err(Error::Eval("trace policy is invalid".to_owned())),
370 }
371 }
372}
373
374impl PartialEq for Arranger {
375 fn eq(&self, other: &Self) -> bool {
376 self.to_expr().canonical_eq(&other.to_expr())
377 }
378}
379
380impl Eq for Arranger {}
381
382impl PartialEq for ArrangerPlacement {
383 fn eq(&self, other: &Self) -> bool {
384 self.to_expr().canonical_eq(&other.to_expr())
385 }
386}
387
388impl Eq for ArrangerPlacement {}
389
390impl PartialEq for PlayableRef {
391 fn eq(&self, other: &Self) -> bool {
392 self.to_expr().canonical_eq(&other.to_expr())
393 }
394}
395
396impl Eq for PlayableRef {}
397
398fn time_expr(time: Time) -> Expr {
399 map(vec![
400 ("numer", Expr::String(time.numer().to_string())),
401 ("denom", Expr::String(time.denom().to_string())),
402 ])
403}
404
405fn time_from_expr(expr: &Expr) -> Result<Time> {
406 let entries = expr_map(expr, "time")?;
407 let denominator = expr_i64(lookup_required(entries, "denom")?, "time denominator")?;
408 if denominator == 0 {
409 return Err(Error::Eval("time denominator cannot be zero".to_owned()));
410 }
411 Ok(Time::new(
412 expr_i64(lookup_required(entries, "numer")?, "time numerator")?,
413 denominator,
414 ))
415}
416
417fn pitch_expr(pitch: Pitch) -> Expr {
418 Expr::String(
419 pitch
420 .to_midi()
421 .map(|midi| format!("midi:{midi}"))
422 .unwrap_or_else(|| format!("semitone:{}", pitch.semitone())),
423 )
424}
425
426fn pitch_from_expr(expr: &Expr) -> Result<Pitch> {
427 let value = expr_string(expr, "pitch")?;
428 if let Some(midi) = value.strip_prefix("midi:") {
429 return Ok(Pitch::from_midi(
430 midi.parse::<u8>()
431 .map_err(|_| Error::Eval("MIDI pitch is invalid".to_owned()))?,
432 ));
433 }
434 if let Some(semitone) = value.strip_prefix("semitone:") {
435 return Ok(Pitch::from_semitone(semitone.parse::<i32>().map_err(
436 |_| Error::Eval("semitone pitch is invalid".to_owned()),
437 )?));
438 }
439 crate::parse_pitch(value).map_err(|_| Error::Eval("pitch is invalid".to_owned()))
440}
441
442fn lane_target_expr(target: &LaneTarget) -> Expr {
443 match target {
444 LaneTarget::Instrument(symbol) => target_expr("instrument", symbol),
445 LaneTarget::Stream(symbol) => target_expr("stream", symbol),
446 LaneTarget::Control(symbol) => target_expr("control", symbol),
447 LaneTarget::None => map(vec![("kind", tag_expr("none"))]),
448 }
449}
450
451fn lane_target_from_expr(expr: &Expr) -> Result<LaneTarget> {
452 let entries = expr_map(expr, "lane target")?;
453 match symbol_name(lookup_required(entries, "kind")?, "lane target kind")? {
454 "instrument" => Ok(LaneTarget::Instrument(expr_symbol(
455 lookup_required(entries, "symbol")?,
456 "target symbol",
457 )?)),
458 "stream" => Ok(LaneTarget::Stream(expr_symbol(
459 lookup_required(entries, "symbol")?,
460 "target symbol",
461 )?)),
462 "control" => Ok(LaneTarget::Control(expr_symbol(
463 lookup_required(entries, "symbol")?,
464 "target symbol",
465 )?)),
466 "none" => Ok(LaneTarget::None),
467 _ => Err(Error::Eval("lane target kind is invalid".to_owned())),
468 }
469}
470
471fn value_transform<T: ToString>(kind: &'static str, value: T) -> Expr {
472 map(vec![
473 ("tag", tag_expr("transform")),
474 ("kind", tag_expr(kind)),
475 ("value", Expr::String(value.to_string())),
476 ])
477}
478
479fn value_remap<T: ToString>(kind: &'static str, value: T) -> Expr {
480 map(vec![
481 ("tag", tag_expr("pitch-remap")),
482 ("kind", tag_expr(kind)),
483 ("value", Expr::String(value.to_string())),
484 ])
485}
486
487fn drum_key_expr((from, to): &(u8, u8)) -> Expr {
488 map(vec![
489 ("from", Expr::String(from.to_string())),
490 ("to", Expr::String(to.to_string())),
491 ])
492}
493
494fn drum_key_from_expr(expr: &Expr) -> Result<(u8, u8)> {
495 let item = expr_map(expr, "drum key item")?;
496 Ok((
497 expr_u8(lookup_required(item, "from")?, "drum key from")?,
498 expr_u8(lookup_required(item, "to")?, "drum key to")?,
499 ))
500}
501
502fn symbolic_remap_expr(kind: &'static str, symbol: &Symbol) -> Expr {
503 map(vec![
504 ("tag", tag_expr("pitch-remap")),
505 ("kind", tag_expr(kind)),
506 ("symbol", Expr::Symbol(symbol.clone())),
507 ])
508}
509
510fn symbolic_remap(
511 entries: &[(Expr, Expr)],
512 build: impl FnOnce(Symbol) -> PitchRemap,
513) -> Result<PitchRemap> {
514 Ok(build(expr_symbol(
515 lookup_required(entries, "symbol")?,
516 "pitch remap symbol",
517 )?))
518}
519
520fn target_expr(kind: &'static str, symbol: &Symbol) -> Expr {
521 map(vec![
522 ("kind", tag_expr(kind)),
523 ("symbol", Expr::Symbol(symbol.clone())),
524 ])
525}
526
527fn map(entries: Vec<(&'static str, Expr)>) -> Expr {
528 Expr::Map(
529 entries
530 .into_iter()
531 .map(|(key, value)| (field(key), value))
532 .collect(),
533 )
534}
535
536fn field(name: &'static str) -> Expr {
537 sim_value::build::qsym(NS, name)
538}
539
540fn tag(name: &'static str) -> Symbol {
541 Symbol::qualified(NS, name)
542}
543
544fn tag_expr(name: &'static str) -> Expr {
545 Expr::Symbol(tag(name))
546}
547
548fn expr_map<'a>(expr: &'a Expr, context: &str) -> Result<&'a [(Expr, Expr)]> {
549 match expr {
550 Expr::Map(entries) => Ok(entries),
551 _ => Err(Error::Eval(format!("{context} must be a map"))),
552 }
553}
554
555fn expr_vector<'a>(expr: &'a Expr, context: &str) -> Result<&'a [Expr]> {
556 match expr {
557 Expr::Vector(items) => Ok(items),
558 _ => Err(Error::Eval(format!("{context} must be a vector"))),
559 }
560}
561
562fn optional_vector<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a [Expr]> {
563 match lookup(entries, name) {
564 Some(expr) => expr_vector(expr, name),
565 None => Ok(&[]),
566 }
567}
568
569fn lookup_required<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
570 lookup(entries, name).ok_or_else(|| Error::Eval(format!("arranger field is missing: {name}")))
571}
572
573fn lookup<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
574 entries.iter().find_map(|(key, value)| match key {
575 Expr::Symbol(symbol)
576 if symbol.namespace.as_deref() == Some(NS) && symbol.name.as_ref() == name =>
577 {
578 Some(value)
579 }
580 _ => None,
581 })
582}
583
584fn expect_tag(entries: &[(Expr, Expr)], name: &'static str, context: &str) -> Result<()> {
585 match lookup(entries, "tag") {
586 Some(Expr::Symbol(symbol))
587 if symbol.namespace.as_deref() == Some(NS) && symbol.name.as_ref() == name =>
588 {
589 Ok(())
590 }
591 Some(_) => Err(Error::Eval(format!("{context} tag is invalid"))),
592 None => Err(Error::Eval(format!("{context} tag is missing"))),
593 }
594}
595
596fn symbol_name<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
597 match expr {
598 Expr::Symbol(symbol) if symbol.namespace.as_deref() == Some(NS) => Ok(symbol.name.as_ref()),
599 _ => Err(Error::Eval(format!("{context} must be an arranger symbol"))),
600 }
601}
602
603fn expr_symbol(expr: &Expr, context: &str) -> Result<Symbol> {
604 match expr {
605 Expr::Symbol(symbol) => Ok(symbol.clone()),
606 _ => Err(Error::Eval(format!("{context} must be a symbol"))),
607 }
608}
609
610fn expr_string<'a>(expr: &'a Expr, context: &str) -> Result<&'a str> {
611 match expr {
612 Expr::String(value) => Ok(value),
613 _ => Err(Error::Eval(format!("{context} must be a string"))),
614 }
615}
616
617fn expr_i16(expr: &Expr, context: &str) -> Result<i16> {
618 parse_number(expr, context)
619}
620
621fn expr_i32(expr: &Expr, context: &str) -> Result<i32> {
622 parse_number(expr, context)
623}
624
625fn expr_i64(expr: &Expr, context: &str) -> Result<i64> {
626 parse_number(expr, context)
627}
628
629fn expr_u8(expr: &Expr, context: &str) -> Result<u8> {
630 parse_number(expr, context)
631}
632
633fn expr_u64(expr: &Expr, context: &str) -> Result<u64> {
634 parse_number(expr, context)
635}
636
637fn parse_number<T>(expr: &Expr, context: &str) -> Result<T>
638where
639 T: std::str::FromStr,
640{
641 let text = match expr {
642 Expr::String(value) => value.as_str(),
643 Expr::Number(value) => value.canonical.as_str(),
644 _ => return Err(Error::Eval(format!("{context} must be a number"))),
645 };
646 text.parse()
647 .map_err(|_| Error::Eval(format!("{context} is invalid")))
648}