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