1use nom::{
7 IResult, Parser,
8 branch::alt,
9 bytes::complete::tag,
10 character::complete::{char, digit1, one_of, space0, space1},
11 combinator::{map, map_res, opt, recognize, value},
12 multi::{many0, separated_list1},
13 sequence::{delimited, preceded},
14};
15
16use crate::ast::mini::*;
17use crate::ast::program::{Accidental, NoteLetter, Ramp};
18
19pub fn parse_mini(input: &str) -> Result<MiniNotation, String> {
21 let trimmed = input.trim();
22 if trimmed.is_empty() {
23 return Ok(MiniNotation {
24 sequence: Sequence { steps: vec![] },
25 });
26 }
27 match parse_sequence(trimmed) {
28 Ok(("", seq)) => {
29 let notation = MiniNotation { sequence: seq };
30 validate_sequence(¬ation.sequence)?;
31 Ok(notation)
32 }
33 Ok((rest, _)) => Err(format!("unexpected trailing input: '{}'", rest)),
34 Err(nom::Err::Failure(error)) => Err(match error.code {
38 FAIL_DOUBLE_VELOCITY => "a step takes one velocity, not two".to_string(),
39 FAIL_ACCENT_VELOCITY => {
40 "'X' is already a velocity of 1.0; write 'x:v' to choose one".to_string()
41 }
42 _ => "a group uses ',' for a chord or '|' for a random choice, not both".to_string(),
43 }),
44 Err(e) => Err(format!("mini-notation parse error: {}", e)),
45 }
46}
47
48const FAIL_MIXED_SEPARATORS: nom::error::ErrorKind = nom::error::ErrorKind::Verify;
50const FAIL_DOUBLE_VELOCITY: nom::error::ErrorKind = nom::error::ErrorKind::Many1;
52const FAIL_ACCENT_VELOCITY: nom::error::ErrorKind = nom::error::ErrorKind::ManyMN;
54
55fn validate_sequence(sequence: &Sequence) -> Result<(), String> {
60 for step in sequence.steps.iter() {
61 for modifier in step.modifiers.iter() {
62 validate_modifier(modifier)?;
63 }
64 validate_velocity(step)?;
65 match &step.atom {
66 Atom::Group(group) => {
67 for layer in group.layers.iter() {
68 if group.mode == GroupMode::Chord
71 && layer
72 .steps
73 .iter()
74 .any(|step| matches!(step.atom, Atom::Solo(_)))
75 {
76 return Err(
77 "solo(..) can't be a chord layer; put it in its own step".to_string()
78 );
79 }
80 validate_sequence(layer)?;
81 }
82 }
83 Atom::Alternation(alternation) => validate_sequence(&alternation.sequence)?,
84 Atom::Solo(solo) => {
85 if solo.high <= solo.low {
86 return Err(format!(
87 "solo({}..{}) needs at least two degrees to walk between",
88 solo.low, solo.high
89 ));
90 }
91 if solo.steps.values().contains(&0) {
92 return Err("solo needs at least one step per cycle".to_string());
93 }
94 }
95 _ => {}
96 }
97 }
98 Ok(())
99}
100
101fn validate_velocity(step: &Step) -> Result<(), String> {
104 let Some(velocity) = &step.velocity else {
105 return Ok(());
106 };
107 match step.atom {
108 Atom::Rest => {
112 return Err(
113 "a rest can't carry a velocity: '~' is silence, so ':v' has nothing to strike"
114 .to_string(),
115 );
116 }
117 Atom::Hold => {
118 return Err("a hold can't carry a velocity: '_' sustains the event before it, which was already struck".to_string());
119 }
120 _ => {}
121 }
122 for value in velocity.values() {
123 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
124 return Err(format!(
125 "velocity {value} is outside the supported range 0.0-1.0"
126 ));
127 }
128 }
129 Ok(())
130}
131
132fn validate_modifier(modifier: &Modifier) -> Result<(), String> {
133 let positive = |value: u32, sigil: &str| {
134 if value == 0 {
135 Err(format!(
136 "'{sigil}0' is not a count; use at least '{sigil}1'"
137 ))
138 } else {
139 Ok(())
140 }
141 };
142 match modifier {
143 Modifier::Repeat(count) => {
144 for value in count.values() {
147 positive(value, "*")?;
148 }
149 Ok(())
150 }
151 Modifier::Replicate(0) => Err("'!0' is not a count; use at least '!1'".to_string()),
152 Modifier::Slow(0) => Err("'/0' is not a count; use at least '/1'".to_string()),
153 Modifier::Weight(0) => Err("'@0' is not a weight; use at least '@1'".to_string()),
154 Modifier::Euclidean(_, positions, _) => {
155 if positions.values().contains(&0) {
156 return Err("a Euclidean rhythm needs at least one step".to_string());
157 }
158 Ok(())
159 }
160 Modifier::Drop(Some(probability)) => {
161 for value in probability.values() {
162 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
163 return Err(format!(
164 "drop probability {value} is outside the supported range 0.0-1.0"
165 ));
166 }
167 }
168 Ok(())
169 }
170 _ => Ok(()),
171 }
172}
173
174fn parse_sequence(input: &str) -> IResult<&str, Sequence> {
177 let (input, _) = space0(input)?;
178 let (input, steps) = separated_list1(space1, parse_step).parse(input)?;
179 let (input, _) = space0(input)?;
180 Ok((input, Sequence { steps }))
181}
182
183fn parse_step(input: &str) -> IResult<&str, Step> {
186 let (mut input, accented) = match parse_accent(input) {
189 Ok((rest, ())) => (rest, true),
190 Err(_) => (input, false),
191 };
192 let (rest, atom) = if accented {
193 (input, Atom::Trigger)
194 } else {
195 parse_atom(input)?
196 };
197 input = rest;
198 let mut modifiers = Vec::new();
199 let mut velocity = accented.then(|| Ramp::fixed(1.0));
200 loop {
205 if let Ok((rest, next)) = parse_velocity(input) {
206 if velocity.is_some() {
207 let code = if accented {
208 FAIL_ACCENT_VELOCITY
209 } else {
210 FAIL_DOUBLE_VELOCITY
211 };
212 return Err(nom::Err::Failure(nom::error::Error::new(input, code)));
213 }
214 velocity = Some(next);
215 input = rest;
216 continue;
217 }
218 match parse_modifier(input) {
219 Ok((rest, modifier)) => {
220 modifiers.push(modifier);
221 input = rest;
222 }
223 Err(_) => break,
224 }
225 }
226 Ok((
227 input,
228 Step {
229 atom,
230 modifiers,
231 velocity,
232 },
233 ))
234}
235
236fn parse_accent(input: &str) -> IResult<&str, ()> {
238 let (input, _) = char('X').parse(input)?;
239 if input
240 .chars()
241 .next()
242 .map(|c| c.is_ascii_alphanumeric())
243 .unwrap_or(false)
244 {
245 return Err(nom::Err::Error(nom::error::Error::new(
246 input,
247 nom::error::ErrorKind::Char,
248 )));
249 }
250 Ok((input, ()))
251}
252
253fn parse_velocity(input: &str) -> IResult<&str, Ramp<f64>> {
255 preceded(char(':'), parse_ramp_f64).parse(input)
256}
257
258fn parse_atom(input: &str) -> IResult<&str, Atom> {
261 alt((
262 parse_solo,
263 parse_group,
264 parse_alternation,
265 parse_note_atom,
266 parse_degree_atom,
267 parse_trigger,
268 parse_rest,
269 parse_hold,
270 ))
271 .parse(input)
272}
273
274fn parse_note_atom(input: &str) -> IResult<&str, Atom> {
275 map(parse_note, Atom::Note).parse(input)
276}
277
278fn parse_note(input: &str) -> IResult<&str, Note> {
279 let (input, letter) = parse_note_letter(input)?;
280 let (input, accidental) = parse_accidental(input, letter)?;
281 let (input, octave) = parse_octave(input)?;
282 Ok((
283 input,
284 Note {
285 letter,
286 accidental,
287 octave,
288 },
289 ))
290}
291
292fn parse_note_letter(input: &str) -> IResult<&str, NoteLetter> {
293 alt((
294 value(NoteLetter::C, char('c')),
295 value(NoteLetter::D, char('d')),
296 value(NoteLetter::E, char('e')),
297 value(NoteLetter::F, char('f')),
298 value(NoteLetter::G, char('g')),
299 value(NoteLetter::A, char('a')),
300 value(NoteLetter::B, char('b')),
301 ))
302 .parse(input)
303}
304
305fn parse_accidental(input: &str, letter: NoteLetter) -> IResult<&str, Accidental> {
309 if letter == NoteLetter::B {
310 if let Ok((rest, _)) = tag::<&str, &str, nom::error::Error<&str>>("##").parse(input) {
312 return Ok((rest, Accidental::DoubleSharp));
313 }
314 if let Ok((rest, _)) = char::<&str, nom::error::Error<&str>>('#').parse(input) {
315 return Ok((rest, Accidental::Sharp));
316 }
317 if let Some(after_b) = input.strip_prefix('b')
319 && after_b.starts_with(|c: char| c.is_ascii_digit())
320 {
321 return Ok((after_b, Accidental::Flat));
322 }
323 Ok((input, Accidental::Natural))
324 } else {
325 let result: IResult<&str, Accidental> = alt((
327 value(Accidental::DoubleSharp, tag("##")),
328 value(Accidental::Sharp, char('#')),
329 value(Accidental::DoubleFlat, tag("bb")),
330 value(Accidental::Flat, char('b')),
331 ))
332 .parse(input);
333 match result {
334 Ok(r) => Ok(r),
335 Err(_) => Ok((input, Accidental::Natural)),
336 }
337 }
338}
339
340fn parse_octave(input: &str) -> IResult<&str, u8> {
341 map_res(one_of("0123456789"), |c: char| {
342 c.to_digit(10).map(|d| d as u8).ok_or("invalid octave")
343 })
344 .parse(input)
345}
346
347fn parse_degree_atom(input: &str) -> IResult<&str, Atom> {
348 if input.starts_with('-') {
350 let (input, _) = char('-').parse(input)?;
351 let (input, digits) = digit1(input)?;
352 let n: i32 = digits.parse().map_err(|_| {
353 nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
354 })?;
355 return Ok((input, Atom::Degree(-n)));
356 }
357 let (input, digits) = digit1(input)?;
359 let n: i32 = digits.parse().map_err(|_| {
360 nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
361 })?;
362 Ok((input, Atom::Degree(n)))
363}
364
365fn parse_trigger(input: &str) -> IResult<&str, Atom> {
366 let (input, _) = char('x').parse(input)?;
367 if input
369 .chars()
370 .next()
371 .map(|c| c.is_ascii_alphanumeric())
372 .unwrap_or(false)
373 {
374 return Err(nom::Err::Error(nom::error::Error::new(
375 input,
376 nom::error::ErrorKind::Char,
377 )));
378 }
379 Ok((input, Atom::Trigger))
380}
381
382fn parse_rest(input: &str) -> IResult<&str, Atom> {
383 value(Atom::Rest, char('~')).parse(input)
384}
385
386fn parse_hold(input: &str) -> IResult<&str, Atom> {
387 value(Atom::Hold, char('_')).parse(input)
388}
389
390fn parse_solo(input: &str) -> IResult<&str, Atom> {
393 let (input, _) = tag("solo(").parse(input)?;
394 let (input, _) = space0(input)?;
395 let (input, low) = parse_i32(input)?;
396 let (input, _) = tag("..").parse(input)?;
397 let (input, high) = parse_i32(input)?;
398 let (input, _) = delimited(space0, char(','), space0).parse(input)?;
399 let (input, steps) = parse_ramp_u32(input)?;
400 let (input, _) = space0(input)?;
401 let (input, _) = char(')').parse(input)?;
402 Ok((input, Atom::Solo(Solo { low, high, steps })))
403}
404
405fn parse_i32(input: &str) -> IResult<&str, i32> {
406 let (rest, negative) = opt(char('-')).parse(input)?;
407 let (rest, digits) = digit1(rest)?;
408 let value: i32 = digits
409 .parse()
410 .map_err(|_| nom::Err::Error(nom::error::Error::new(rest, nom::error::ErrorKind::Digit)))?;
411 Ok((rest, if negative.is_some() { -value } else { value }))
412}
413
414fn parse_group(input: &str) -> IResult<&str, Atom> {
417 let (input, _) = char('[').parse(input)?;
418 let (mut input, first) = parse_sequence(input)?;
420 let mut layers = vec![first];
421 let mut chord = false;
422 let mut random = false;
423 while let Ok((rest, separator)) =
424 one_of::<&str, &str, nom::error::Error<&str>>(",|").parse(input)
425 {
426 let (rest, layer) = parse_sequence(rest)?;
427 match separator {
428 ',' => chord = true,
429 _ => random = true,
430 }
431 layers.push(layer);
432 input = rest;
433 }
434 let (input, _) = char(']').parse(input)?;
435 if chord && random {
436 return Err(nom::Err::Failure(nom::error::Error::new(
439 input,
440 FAIL_MIXED_SEPARATORS,
441 )));
442 }
443 let mode = match (chord, random) {
444 (true, _) => GroupMode::Chord,
445 (_, true) => GroupMode::Random,
446 _ => GroupMode::Subdivide,
447 };
448 Ok((input, Atom::Group(Group { mode, layers })))
449}
450
451fn parse_alternation(input: &str) -> IResult<&str, Atom> {
454 let (input, _) = char('<').parse(input)?;
455 let (input, _) = space0(input)?;
456 let (input, sequence) = parse_sequence(input)?;
457 let (input, _) = space0(input)?;
458 let (input, _) = char('>').parse(input)?;
459 Ok((input, Atom::Alternation(Alternation { sequence })))
460}
461
462fn parse_modifier(input: &str) -> IResult<&str, Modifier> {
465 alt((
466 parse_repeat,
467 parse_slow_mod,
468 parse_replicate,
469 parse_euclidean,
470 parse_drop,
471 parse_weight,
472 ))
473 .parse(input)
474}
475
476fn parse_repeat(input: &str) -> IResult<&str, Modifier> {
477 let (input, _) = char('*').parse(input)?;
478 let (input, n) = parse_ramp_u32(input)?;
479 Ok((input, Modifier::Repeat(n)))
480}
481
482fn parse_slow_mod(input: &str) -> IResult<&str, Modifier> {
483 let (input, _) = char('/').parse(input)?;
484 let (input, n) = parse_u32(input)?;
485 Ok((input, Modifier::Slow(n)))
486}
487
488fn parse_replicate(input: &str) -> IResult<&str, Modifier> {
489 let (input, _) = char('!').parse(input)?;
490 let (input, n) = parse_u32(input)?;
491 Ok((input, Modifier::Replicate(n)))
492}
493
494fn parse_euclidean(input: &str) -> IResult<&str, Modifier> {
495 let (input, _) = char('(').parse(input)?;
496 let (input, _) = space0(input)?;
497 let (input, beats) = parse_ramp_u32(input)?;
498 let (input, _) = delimited(space0, char(','), space0).parse(input)?;
499 let (input, steps) = parse_ramp_u32(input)?;
500 let (input, offset) =
501 opt(preceded(delimited(space0, char(','), space0), parse_u32)).parse(input)?;
502 let (input, _) = space0(input)?;
503 let (input, _) = char(')').parse(input)?;
504 Ok((input, Modifier::Euclidean(beats, steps, offset)))
505}
506
507fn parse_drop(input: &str) -> IResult<&str, Modifier> {
508 let (input, _) = char('?').parse(input)?;
509 let (input, probability) = opt(parse_ramp_f64).parse(input)?;
512 Ok((input, Modifier::Drop(probability)))
513}
514
515fn parse_weight(input: &str) -> IResult<&str, Modifier> {
516 let (input, _) = char('@').parse(input)?;
517 let (input, n) = parse_u32(input)?;
518 Ok((input, Modifier::Weight(n)))
519}
520
521fn parse_u32(input: &str) -> IResult<&str, u32> {
522 map_res(digit1, |s: &str| s.parse::<u32>()).parse(input)
523}
524
525fn parse_ramp_u32(input: &str) -> IResult<&str, Ramp<u32>> {
531 if let Some(result) = parse_r_form(input, |part| {
532 part.parse::<u32>().map_err(|error| error.to_string())
533 }) {
534 return result;
535 }
536 let (input, first) = parse_u32(input)?;
537 if let Ok((rest, to)) = preceded(tag(".."), parse_u32).parse(input) {
538 return Ok((rest, Ramp::Sweep { from: first, to }));
539 }
540 let (input, steps) = many0(preceded(tag(">"), parse_u32)).parse(input)?;
541 Ok((input, Ramp::steps(first, steps)))
542}
543
544fn parse_ramp_f64(input: &str) -> IResult<&str, Ramp<f64>> {
545 if let Some(result) = parse_r_form(input, |part| {
546 part.parse::<f64>().map_err(|error| error.to_string())
547 }) {
548 return result;
549 }
550 let (input, first) = parse_f64(input)?;
551 if let Ok((rest, to)) = preceded(tag(".."), parse_f64).parse(input) {
552 return Ok((rest, Ramp::Sweep { from: first, to }));
553 }
554 let (input, steps) = many0(preceded(tag(">"), parse_f64)).parse(input)?;
555 Ok((input, Ramp::steps(first, steps)))
556}
557
558fn parse_r_form<T: Copy>(
563 input: &str,
564 parse: impl Fn(&str) -> Result<T, String>,
565) -> Option<IResult<&str, Ramp<T>>> {
566 let inner = input.strip_prefix("r(")?;
567 let close = inner.find(')')?;
568 let text = format!("r({})", &inner[..close]);
569 Some(
570 match crate::parser::lines::ramp_from_text(&text, "r", &parse) {
571 Ok(ramp) => Ok((&inner[close + 1..], ramp)),
572 Err(_) => Err(nom::Err::Error(nom::error::Error::new(
573 input,
574 nom::error::ErrorKind::Verify,
575 ))),
576 },
577 )
578}
579
580fn parse_f64(input: &str) -> IResult<&str, f64> {
581 map_res(recognize((digit1, opt((char('.'), digit1)))), |s: &str| {
582 s.parse::<f64>()
583 })
584 .parse(input)
585}
586
587#[cfg(test)]
592mod tests {
593 use super::*;
594 use crate::ast::program::RampCurve;
595 use pretty_assertions::assert_eq;
596
597 fn n(letter: NoteLetter, acc: Accidental, oct: u8) -> Atom {
599 Atom::Note(Note {
600 letter,
601 accidental: acc,
602 octave: oct,
603 })
604 }
605
606 fn nat(letter: NoteLetter, oct: u8) -> Atom {
607 n(letter, Accidental::Natural, oct)
608 }
609
610 #[test]
613 fn r_form_ranges_fit_inside_mini_notation() {
614 let m = parse_mini("x(r(2,16,8),4)").unwrap();
615 let modifiers = &m.sequence.steps[0].modifiers;
616 assert_eq!(
617 modifiers,
618 &vec![Modifier::Euclidean(
619 Ramp::Timed {
620 ramp: Box::new(Ramp::Sweep { from: 2, to: 16 }),
621 span_divisions: 8.0,
622 curve: RampCurve::Linear,
623 },
624 Ramp::fixed(4),
625 None
626 )]
627 );
628
629 let m = parse_mini("x*r(2,8,4,osc)").unwrap();
630 assert_eq!(
631 m.sequence.steps[0].modifiers,
632 vec![Modifier::Repeat(Ramp::Timed {
633 ramp: Box::new(Ramp::Sweep { from: 2, to: 8 }),
634 span_divisions: 4.0,
635 curve: RampCurve::Oscillate,
636 })]
637 );
638
639 let m = parse_mini("x:r(0.3,1.0,2)").unwrap();
640 assert!(m.sequence.steps[0].velocity.as_ref().unwrap().travels());
641 }
642
643 #[test]
644 fn test_note_c4() {
645 let m = parse_mini("c4").unwrap();
646 assert_eq!(m.sequence.steps.len(), 1);
647 assert_eq!(m.sequence.steps[0].atom, nat(NoteLetter::C, 4));
648 }
649
650 #[test]
651 fn test_note_eb3() {
652 let m = parse_mini("eb3").unwrap();
653 assert_eq!(
654 m.sequence.steps[0].atom,
655 n(NoteLetter::E, Accidental::Flat, 3)
656 );
657 }
658
659 #[test]
660 fn test_note_f_sharp_5() {
661 let m = parse_mini("f#5").unwrap();
662 assert_eq!(
663 m.sequence.steps[0].atom,
664 n(NoteLetter::F, Accidental::Sharp, 5)
665 );
666 }
667
668 #[test]
669 fn test_note_bb3_is_b_flat() {
670 let m = parse_mini("bb3").unwrap();
671 assert_eq!(
672 m.sequence.steps[0].atom,
673 n(NoteLetter::B, Accidental::Flat, 3)
674 );
675 }
676
677 #[test]
678 fn test_note_b4_natural() {
679 let m = parse_mini("b4").unwrap();
680 assert_eq!(
681 m.sequence.steps[0].atom,
682 n(NoteLetter::B, Accidental::Natural, 4)
683 );
684 }
685
686 #[test]
687 fn test_note_b_sharp() {
688 let m = parse_mini("b#4").unwrap();
689 assert_eq!(
690 m.sequence.steps[0].atom,
691 n(NoteLetter::B, Accidental::Sharp, 4)
692 );
693 }
694
695 #[test]
698 fn test_sequence_three_notes() {
699 let m = parse_mini("c4 e4 g4").unwrap();
700 assert_eq!(m.sequence.steps.len(), 3);
701 assert_eq!(m.sequence.steps[0].atom, nat(NoteLetter::C, 4));
702 assert_eq!(m.sequence.steps[1].atom, nat(NoteLetter::E, 4));
703 assert_eq!(m.sequence.steps[2].atom, nat(NoteLetter::G, 4));
704 }
705
706 #[test]
707 fn test_rest_and_hold() {
708 let m = parse_mini("c4 ~ _ e4").unwrap();
709 assert_eq!(m.sequence.steps.len(), 4);
710 assert_eq!(m.sequence.steps[0].atom, nat(NoteLetter::C, 4));
711 assert_eq!(m.sequence.steps[1].atom, Atom::Rest);
712 assert_eq!(m.sequence.steps[2].atom, Atom::Hold);
713 assert_eq!(m.sequence.steps[3].atom, nat(NoteLetter::E, 4));
714 }
715
716 #[test]
717 fn test_trigger() {
718 let m = parse_mini("x ~ x ~").unwrap();
719 assert_eq!(m.sequence.steps.len(), 4);
720 assert_eq!(m.sequence.steps[0].atom, Atom::Trigger);
721 assert_eq!(m.sequence.steps[1].atom, Atom::Rest);
722 assert_eq!(m.sequence.steps[2].atom, Atom::Trigger);
723 assert_eq!(m.sequence.steps[3].atom, Atom::Rest);
724 }
725
726 #[test]
727 fn test_scale_degrees() {
728 let m = parse_mini("0 2 4 6").unwrap();
729 assert_eq!(m.sequence.steps.len(), 4);
730 assert_eq!(m.sequence.steps[0].atom, Atom::Degree(0));
731 assert_eq!(m.sequence.steps[1].atom, Atom::Degree(2));
732 assert_eq!(m.sequence.steps[2].atom, Atom::Degree(4));
733 assert_eq!(m.sequence.steps[3].atom, Atom::Degree(6));
734 }
735
736 #[test]
739 fn test_repeat() {
740 let m = parse_mini("x*4").unwrap();
741 assert_eq!(m.sequence.steps[0].atom, Atom::Trigger);
742 assert_eq!(
743 m.sequence.steps[0].modifiers,
744 vec![Modifier::Repeat(Ramp::fixed(4))]
745 );
746 }
747
748 #[test]
749 fn test_slow_modifier() {
750 let m = parse_mini("[c4 e4 g4]/2").unwrap();
751 assert!(matches!(m.sequence.steps[0].atom, Atom::Group(_)));
752 assert_eq!(m.sequence.steps[0].modifiers, vec![Modifier::Slow(2)]);
753 }
754
755 #[test]
756 fn test_replicate() {
757 let m = parse_mini("c4!3").unwrap();
758 assert_eq!(m.sequence.steps[0].modifiers, vec![Modifier::Replicate(3)]);
759 }
760
761 #[test]
762 fn test_euclidean() {
763 let m = parse_mini("c4(3,8)").unwrap();
764 assert_eq!(
765 m.sequence.steps[0].modifiers,
766 vec![Modifier::Euclidean(Ramp::fixed(3), Ramp::fixed(8), None)]
767 );
768 }
769
770 #[test]
771 fn test_euclidean_with_offset() {
772 let m = parse_mini("x(5,8,2)").unwrap();
773 assert_eq!(
774 m.sequence.steps[0].modifiers,
775 vec![Modifier::Euclidean(Ramp::fixed(5), Ramp::fixed(8), Some(2))]
776 );
777 }
778
779 #[test]
780 fn test_drop() {
781 let m = parse_mini("c4?").unwrap();
782 assert_eq!(m.sequence.steps[0].modifiers, vec![Modifier::Drop(None)]);
783 }
784
785 #[test]
786 fn test_weight() {
787 let m = parse_mini("c4@3 e4").unwrap();
788 assert_eq!(m.sequence.steps[0].modifiers, vec![Modifier::Weight(3)]);
789 assert!(m.sequence.steps[1].modifiers.is_empty());
790 }
791
792 #[test]
795 fn test_group() {
796 let m = parse_mini("[c4 e4] g4").unwrap();
797 assert_eq!(m.sequence.steps.len(), 2);
798 if let Atom::Group(ref g) = m.sequence.steps[0].atom {
799 assert_eq!(g.mode, GroupMode::Subdivide);
800 assert_eq!(g.layers.len(), 1);
801 assert_eq!(g.layers[0].steps.len(), 2);
802 } else {
803 panic!("expected group");
804 }
805 assert_eq!(m.sequence.steps[1].atom, nat(NoteLetter::G, 4));
806 }
807
808 #[test]
809 fn test_chord() {
810 let m = parse_mini("[c3,e3,g3]").unwrap();
811 assert_eq!(m.sequence.steps.len(), 1);
812 if let Atom::Group(ref g) = m.sequence.steps[0].atom {
813 assert_eq!(g.mode, GroupMode::Chord);
814 assert_eq!(g.layers.len(), 3);
815 assert_eq!(g.layers[0].steps[0].atom, nat(NoteLetter::C, 3));
816 assert_eq!(g.layers[1].steps[0].atom, nat(NoteLetter::E, 3));
817 assert_eq!(g.layers[2].steps[0].atom, nat(NoteLetter::G, 3));
818 } else {
819 panic!("expected group/chord");
820 }
821 }
822
823 #[test]
824 fn test_nested_groups() {
825 let m = parse_mini("[c4 [e4 g4]] b4").unwrap();
826 assert_eq!(m.sequence.steps.len(), 2);
827 if let Atom::Group(ref outer) = m.sequence.steps[0].atom {
828 assert_eq!(outer.layers[0].steps.len(), 2);
829 assert!(matches!(outer.layers[0].steps[1].atom, Atom::Group(_)));
830 } else {
831 panic!("expected nested group");
832 }
833 }
834
835 #[test]
838 fn test_alternation() {
839 let m = parse_mini("<c4 e4 g4>").unwrap();
840 assert_eq!(m.sequence.steps.len(), 1);
841 if let Atom::Alternation(ref a) = m.sequence.steps[0].atom {
842 assert_eq!(a.sequence.steps.len(), 3);
843 } else {
844 panic!("expected alternation");
845 }
846 }
847
848 #[test]
849 fn test_alternation_in_sequence() {
850 let m = parse_mini("c4 <e4 g4> c5").unwrap();
851 assert_eq!(m.sequence.steps.len(), 3);
852 assert_eq!(m.sequence.steps[0].atom, nat(NoteLetter::C, 4));
853 assert!(matches!(m.sequence.steps[1].atom, Atom::Alternation(_)));
854 assert_eq!(m.sequence.steps[2].atom, nat(NoteLetter::C, 5));
855 }
856
857 #[test]
860 fn test_empty_notation() {
861 let m = parse_mini("").unwrap();
862 assert_eq!(m.sequence.steps.len(), 0);
863 }
864
865 #[test]
866 fn test_whitespace_only() {
867 let m = parse_mini(" ").unwrap();
868 assert_eq!(m.sequence.steps.len(), 0);
869 }
870
871 #[test]
874 fn test_drum_pattern() {
875 let m = parse_mini("x ~ [x x] ~").unwrap();
876 assert_eq!(m.sequence.steps.len(), 4);
877 assert_eq!(m.sequence.steps[0].atom, Atom::Trigger);
878 assert_eq!(m.sequence.steps[1].atom, Atom::Rest);
879 assert!(matches!(m.sequence.steps[2].atom, Atom::Group(_)));
880 assert_eq!(m.sequence.steps[3].atom, Atom::Rest);
881 }
882
883 #[test]
884 fn test_chord_sequence() {
885 let m = parse_mini("[c3,e3,g3] ~ [f3,a3,c4] ~").unwrap();
886 assert_eq!(m.sequence.steps.len(), 4);
887 if let Atom::Group(ref g) = m.sequence.steps[0].atom {
888 assert_eq!(g.layers.len(), 3);
889 } else {
890 panic!("expected chord");
891 }
892 }
893
894 #[test]
895 fn test_repeat_group() {
896 let m = parse_mini("[c4 e4]*3").unwrap();
897 assert_eq!(m.sequence.steps.len(), 1);
898 assert!(matches!(m.sequence.steps[0].atom, Atom::Group(_)));
899 assert_eq!(
900 m.sequence.steps[0].modifiers,
901 vec![Modifier::Repeat(Ramp::fixed(3))]
902 );
903 }
904
905 #[test]
908 fn test_stacked_repeat_and_drop() {
909 let m = parse_mini("x*8?").unwrap();
910 assert_eq!(m.sequence.steps.len(), 1);
911 assert_eq!(m.sequence.steps[0].atom, Atom::Trigger);
912 assert_eq!(
913 m.sequence.steps[0].modifiers,
914 vec![Modifier::Repeat(Ramp::fixed(8)), Modifier::Drop(None)]
915 );
916 }
917
918 #[test]
919 fn test_stacked_euclidean_and_drop_keeps_written_order() {
920 let m = parse_mini("x(5,8)?0.25").unwrap();
921 assert_eq!(
922 m.sequence.steps[0].modifiers,
923 vec![
924 Modifier::Euclidean(Ramp::fixed(5), Ramp::fixed(8), None),
925 Modifier::Drop(Some(Ramp::fixed(0.25)))
926 ]
927 );
928 }
929
930 #[test]
931 fn test_stacked_weight_and_repeat() {
932 let m = parse_mini("[c4 e4]*2@3 g4").unwrap();
933 assert_eq!(
934 m.sequence.steps[0].modifiers,
935 vec![Modifier::Repeat(Ramp::fixed(2)), Modifier::Weight(3)]
936 );
937 }
938
939 #[test]
942 fn test_drop_with_probability() {
943 let m = parse_mini("x?0.3").unwrap();
944 assert_eq!(
945 m.sequence.steps[0].modifiers,
946 vec![Modifier::Drop(Some(Ramp::fixed(0.3)))]
947 );
948 }
949
950 #[test]
953 fn test_solo_atom() {
954 let m = parse_mini("solo(0..7, 8)").unwrap();
955 assert_eq!(
956 m.sequence.steps[0].atom,
957 Atom::Solo(Solo {
958 low: 0,
959 high: 7,
960 steps: Ramp::fixed(8)
961 })
962 );
963 let m = parse_mini("solo(-3..4,6)").unwrap();
965 assert_eq!(
966 m.sequence.steps[0].atom,
967 Atom::Solo(Solo {
968 low: -3,
969 high: 4,
970 steps: Ramp::fixed(6)
971 })
972 );
973 let m = parse_mini("solo(0..12, 4..16)").unwrap();
975 assert_eq!(
976 m.sequence.steps[0].atom,
977 Atom::Solo(Solo {
978 low: 0,
979 high: 12,
980 steps: Ramp::Sweep { from: 4, to: 16 }
981 })
982 );
983 }
984
985 #[test]
986 fn test_solo_composes_with_sequences_and_modifiers() {
987 let m = parse_mini("~ solo(0..7,4)").unwrap();
989 assert_eq!(m.sequence.steps.len(), 2);
990 assert!(matches!(m.sequence.steps[1].atom, Atom::Solo(_)));
991 let m = parse_mini("solo(0..7,8)?0.3").unwrap();
993 assert_eq!(
994 m.sequence.steps[0].modifiers,
995 vec![Modifier::Drop(Some(Ramp::fixed(0.3)))]
996 );
997 }
998
999 #[test]
1000 fn test_solo_validation() {
1001 assert!(parse_mini("solo(5..5,8)").is_err());
1003 assert!(parse_mini("solo(7..0,8)").is_err());
1004 assert!(parse_mini("solo(0..7,0)").is_err());
1005 assert!(parse_mini("solo(0..7,4..0)").is_err());
1006 assert!(parse_mini("[c4,solo(0..7,4)]").is_err());
1009 assert!(parse_mini("[c4|solo(0..7,4)]").is_ok());
1010 assert!(parse_mini("[c4 solo(0..7,4)]").is_ok());
1011 }
1012
1013 #[test]
1016 fn test_euclidean_onsets_may_ramp() {
1017 let m = parse_mini("x(4..16,4)").unwrap();
1019 assert_eq!(
1020 m.sequence.steps[0].modifiers,
1021 vec![Modifier::Euclidean(
1022 Ramp::Sweep { from: 4, to: 16 },
1023 Ramp::fixed(4),
1024 None
1025 )]
1026 );
1027 }
1028
1029 #[test]
1030 fn test_ranges_do_not_confuse_the_float_parser() {
1031 let m = parse_mini("x?0.1..0.9").unwrap();
1033 assert_eq!(
1034 m.sequence.steps[0].modifiers,
1035 vec![Modifier::Drop(Some(Ramp::Sweep { from: 0.1, to: 0.9 }))]
1036 );
1037 let m = parse_mini("x*2..8").unwrap();
1038 assert_eq!(
1039 m.sequence.steps[0].modifiers,
1040 vec![Modifier::Repeat(Ramp::Sweep { from: 2, to: 8 })]
1041 );
1042 let m = parse_mini("x(3,4..16,1)").unwrap();
1044 assert_eq!(
1045 m.sequence.steps[0].modifiers,
1046 vec![Modifier::Euclidean(
1047 Ramp::fixed(3),
1048 Ramp::Sweep { from: 4, to: 16 },
1049 Some(1)
1050 )]
1051 );
1052 }
1053
1054 #[test]
1055 fn test_a_step_chain_holds_each_value_in_turn() {
1056 let m = parse_mini("x(2>4>8>16,4)").unwrap();
1058 assert_eq!(
1059 m.sequence.steps[0].modifiers,
1060 vec![Modifier::Euclidean(
1061 Ramp::Steps {
1062 first: 2,
1063 rest: vec![4, 8, 16]
1064 },
1065 Ramp::fixed(4),
1066 None
1067 )]
1068 );
1069
1070 let m = parse_mini("x*4>8>16").unwrap();
1071 assert_eq!(
1072 m.sequence.steps[0].modifiers,
1073 vec![Modifier::Repeat(Ramp::Steps {
1074 first: 4,
1075 rest: vec![8, 16]
1076 })]
1077 );
1078
1079 let m = parse_mini("x?0.2>0.6>0.9").unwrap();
1080 assert_eq!(
1081 m.sequence.steps[0].modifiers,
1082 vec![Modifier::Drop(Some(Ramp::Steps {
1083 first: 0.2,
1084 rest: vec![0.6, 0.9]
1085 }))]
1086 );
1087 }
1088
1089 #[test]
1090 fn test_a_step_chain_does_not_swallow_an_alternation_close() {
1091 let m = parse_mini("<x*4 x*8>").unwrap();
1093 let Atom::Alternation(ref alternation) = m.sequence.steps[0].atom else {
1094 panic!(
1095 "expected an alternation, got {:?}",
1096 m.sequence.steps[0].atom
1097 );
1098 };
1099 assert_eq!(alternation.sequence.steps.len(), 2);
1100 assert_eq!(
1101 alternation.sequence.steps[0].modifiers,
1102 vec![Modifier::Repeat(Ramp::fixed(4))]
1103 );
1104 assert_eq!(
1105 alternation.sequence.steps[1].modifiers,
1106 vec![Modifier::Repeat(Ramp::fixed(8))]
1107 );
1108 }
1109
1110 #[test]
1111 fn test_every_stage_of_a_chain_is_validated() {
1112 assert!(parse_mini("x*2>0>8").is_err());
1113 assert!(parse_mini("x(3,4>0)").is_err());
1114 assert!(parse_mini("x?0.2>2.0").is_err());
1115 }
1116
1117 #[test]
1118 fn test_both_ends_of_a_range_are_validated() {
1119 assert!(parse_mini("x*1..0").is_err());
1120 assert!(parse_mini("x(3,4..0)").is_err());
1121 assert!(parse_mini("x?0.5..2").is_err());
1122 }
1123
1124 #[test]
1125 fn test_drop_probability_out_of_range_is_rejected() {
1126 let error = parse_mini("x?2").unwrap_err();
1127 assert!(error.contains("0.0-1.0"), "unexpected message: {error}");
1128 }
1129
1130 #[test]
1131 fn test_bare_drop_then_degree_is_two_steps() {
1132 let m = parse_mini("x? 0").unwrap();
1134 assert_eq!(m.sequence.steps.len(), 2);
1135 assert_eq!(m.sequence.steps[0].modifiers, vec![Modifier::Drop(None)]);
1136 assert_eq!(m.sequence.steps[1].atom, Atom::Degree(0));
1137 }
1138
1139 #[test]
1142 fn test_accent_is_a_full_velocity_trigger() {
1143 let m = parse_mini("X ~ x ~").unwrap();
1144 assert_eq!(m.sequence.steps[0].atom, Atom::Trigger);
1145 assert_eq!(m.sequence.steps[0].velocity, Some(Ramp::fixed(1.0)));
1146 assert_eq!(m.sequence.steps[2].atom, Atom::Trigger);
1148 assert_eq!(m.sequence.steps[2].velocity, None);
1149 assert_eq!(parse_mini("X").unwrap(), parse_mini("x:1.0").unwrap());
1151 }
1152
1153 #[test]
1154 fn test_explicit_velocity_on_any_sounding_atom() {
1155 let velocity = |source: &str| {
1156 parse_mini(source).unwrap().sequence.steps[0]
1157 .velocity
1158 .clone()
1159 };
1160 assert_eq!(velocity("x:0.6"), Some(Ramp::fixed(0.6)));
1161 assert_eq!(velocity("c4:0.35"), Some(Ramp::fixed(0.35)));
1162 assert_eq!(velocity("3:0.5"), Some(Ramp::fixed(0.5)));
1163 assert_eq!(velocity("[c4 e4]:0.8"), Some(Ramp::fixed(0.8)));
1164 assert_eq!(velocity("<c4 e4>:0.8"), Some(Ramp::fixed(0.8)));
1165 assert_eq!(velocity("solo(0..7,8):0.7"), Some(Ramp::fixed(0.7)));
1166 let m = parse_mini("[c4 e4:0.4]:0.9").unwrap();
1168 assert_eq!(m.sequence.steps[0].velocity, Some(Ramp::fixed(0.9)));
1169 let Atom::Group(ref group) = m.sequence.steps[0].atom else {
1170 panic!("expected a group");
1171 };
1172 assert_eq!(group.layers[0].steps[1].velocity, Some(Ramp::fixed(0.4)));
1173 }
1174
1175 #[test]
1176 fn test_velocity_composes_with_slot_generating_modifiers() {
1177 let m = parse_mini("X*4").unwrap();
1180 assert_eq!(
1181 m.sequence.steps[0].modifiers,
1182 vec![Modifier::Repeat(Ramp::fixed(4))]
1183 );
1184 assert_eq!(m.sequence.steps[0].velocity, Some(Ramp::fixed(1.0)));
1185
1186 assert_eq!(
1187 parse_mini("x:0.6*4").unwrap(),
1188 parse_mini("x*4:0.6").unwrap()
1189 );
1190
1191 let m = parse_mini("x:0.6?0.25").unwrap();
1192 assert_eq!(
1193 m.sequence.steps[0].modifiers,
1194 vec![Modifier::Drop(Some(Ramp::fixed(0.25)))]
1195 );
1196 assert_eq!(m.sequence.steps[0].velocity, Some(Ramp::fixed(0.6)));
1197
1198 let m = parse_mini("x(3,8):0.9").unwrap();
1199 assert_eq!(
1200 m.sequence.steps[0].modifiers,
1201 vec![Modifier::Euclidean(Ramp::fixed(3), Ramp::fixed(8), None)]
1202 );
1203 assert_eq!(m.sequence.steps[0].velocity, Some(Ramp::fixed(0.9)));
1204 }
1205
1206 #[test]
1207 fn test_velocity_may_travel() {
1208 let m = parse_mini("x:0.3..0.9").unwrap();
1209 assert_eq!(
1210 m.sequence.steps[0].velocity,
1211 Some(Ramp::Sweep { from: 0.3, to: 0.9 })
1212 );
1213 assert!(m.sequence.steps[0].velocity.as_ref().unwrap().travels());
1214 let m = parse_mini("x:0.3>0.6>0.9").unwrap();
1215 assert_eq!(
1216 m.sequence.steps[0].velocity,
1217 Some(Ramp::Steps {
1218 first: 0.3,
1219 rest: vec![0.6, 0.9]
1220 })
1221 );
1222 let m = parse_mini("<x:0.4 x:0.8>").unwrap();
1224 let Atom::Alternation(ref alternation) = m.sequence.steps[0].atom else {
1225 panic!("expected an alternation");
1226 };
1227 assert_eq!(
1228 alternation.sequence.steps[1].velocity,
1229 Some(Ramp::fixed(0.8))
1230 );
1231 }
1232
1233 #[test]
1234 fn test_velocity_out_of_range_is_rejected_at_every_value() {
1235 let error = parse_mini("x:1.4").unwrap_err();
1236 assert!(
1237 error.contains("1.4") && error.contains("0.0-1.0"),
1238 "{error}"
1239 );
1240 assert!(parse_mini("x:0.5..1.2").unwrap_err().contains("1.2"));
1241 assert!(parse_mini("x:0.2>0.9>1.1").unwrap_err().contains("1.1"));
1242 assert!(parse_mini("[c4 e4:2]").is_err());
1244 }
1245
1246 #[test]
1247 fn test_velocity_on_a_rest_or_a_hold_is_rejected() {
1248 let error = parse_mini("~:0.5").unwrap_err();
1249 assert!(error.contains("rest"), "{error}");
1250 let error = parse_mini("x _:0.5").unwrap_err();
1251 assert!(error.contains("hold"), "{error}");
1252 }
1253
1254 #[test]
1255 fn test_one_velocity_per_step() {
1256 let error = parse_mini("x:0.6:0.8").unwrap_err();
1257 assert!(error.contains("one velocity"), "{error}");
1258 let error = parse_mini("X:0.6").unwrap_err();
1260 assert!(error.contains("already a velocity of 1.0"), "{error}");
1261 }
1262
1263 #[test]
1266 fn test_random_choice_group() {
1267 let m = parse_mini("[c4|e4|g4]").unwrap();
1268 assert_eq!(m.sequence.steps.len(), 1);
1269 if let Atom::Group(ref g) = m.sequence.steps[0].atom {
1270 assert_eq!(g.mode, GroupMode::Random);
1271 assert_eq!(g.layers.len(), 3);
1272 assert_eq!(g.layers[0].steps[0].atom, nat(NoteLetter::C, 4));
1273 assert_eq!(g.layers[2].steps[0].atom, nat(NoteLetter::G, 4));
1274 } else {
1275 panic!("expected random-choice group");
1276 }
1277 }
1278
1279 #[test]
1280 fn test_random_choice_layers_may_be_sequences() {
1281 let m = parse_mini("[x x|x ~ x]").unwrap();
1282 if let Atom::Group(ref g) = m.sequence.steps[0].atom {
1283 assert_eq!(g.mode, GroupMode::Random);
1284 assert_eq!(g.layers[0].steps.len(), 2);
1285 assert_eq!(g.layers[1].steps.len(), 3);
1286 } else {
1287 panic!("expected random-choice group");
1288 }
1289 }
1290
1291 #[test]
1292 fn test_mixing_chord_and_random_separators_is_rejected() {
1293 let error = parse_mini("[c4,e4|g4]").unwrap_err();
1294 assert!(error.contains("not both"), "unexpected message: {error}");
1295 }
1296
1297 #[test]
1300 fn test_euclidean_accepts_more_onsets_than_steps() {
1301 let m = parse_mini("x(9,8)").unwrap();
1303 assert_eq!(
1304 m.sequence.steps[0].modifiers,
1305 vec![Modifier::Euclidean(Ramp::fixed(9), Ramp::fixed(8), None)]
1306 );
1307 assert!(parse_mini("x(20,8)").is_ok());
1308 }
1309
1310 #[test]
1311 fn test_zero_counts_are_rejected() {
1312 assert!(parse_mini("x*0").is_err());
1313 assert!(parse_mini("x!0").is_err());
1314 assert!(parse_mini("x@0").is_err());
1315 assert!(parse_mini("x(3,0)").is_err());
1316 assert!(parse_mini("x(0,8)").is_ok());
1318 }
1319
1320 #[test]
1321 fn test_validation_reaches_nested_layers() {
1322 assert!(parse_mini("[c4 e4*0]").is_err());
1323 assert!(parse_mini("<c4 e4?3>").is_err());
1324 }
1325}