1use crate::defaults::{FloatType, IntegerType};
12use crate::duration::{Duration, DurationType, Tuplet, TupletType};
13use crate::error::{Error, Result};
14use crate::expressions::{Expression, Fermata};
15use crate::makenotation::{make_measures, op_frac};
16use crate::meter::TimeSignature;
17use crate::notation::{Tie, TieType};
18use crate::note::Note;
19use crate::pitch::{Accidental, PitchOptions};
20use crate::rest::Rest;
21use crate::stream::{Stream, StreamElement, StreamEvent, StreamKind};
22
23enum State {
25 Tuplet {
28 actual: u32,
29 normal: u32,
30 tokens: Vec<usize>,
31 },
32 Tie { tokens: Vec<usize> },
34}
35
36fn is_modifier_free(character: char) -> bool {
37 !character.is_whitespace() && character != '_'
38}
39
40fn modifiers_reach_end(text: &[char], at: usize) -> bool {
43 if at == text.len() {
44 return true;
45 }
46 let closed_by = |close: char| -> bool {
47 (at + 1..text.len())
48 .filter(|index| text[*index] == close)
49 .any(|index| modifiers_reach_end(text, index + 1))
50 };
51 match text[at] {
52 '=' => {
53 let mut end = at + 1;
54 while end < text.len() && is_modifier_free(text[end]) {
55 end += 1;
56 }
57 (at + 1..=end)
58 .rev()
59 .any(|stop| modifiers_reach_end(text, stop))
60 }
61 '*' => closed_by('*'),
62 '<' => closed_by('>'),
63 '(' => closed_by(')'),
64 '[' => closed_by(']'),
65 '_' => {
66 at + 1 < text.len()
67 && !text[at + 1].is_whitespace()
68 && text[at + 1] != '='
69 && modifiers_reach_end(text, at + 2)
70 }
71 _ => false,
72 }
73}
74
75fn duration_ends(text: &[char], at: usize) -> Vec<usize> {
78 let run = |from: usize, test: fn(&char) -> bool| -> usize {
79 from + text[from..].iter().take_while(|c| test(c)).count()
80 };
81 let digit: fn(&char) -> bool = |c| c.is_ascii_digit();
82 let dot: fn(&char) -> bool = |c| *c == '.';
83 let mut ends = vec![at];
84 let digits = run(at, digit);
85 if digits > at {
86 for stop in at + 1..=digits {
87 for dots in stop..=run(stop, dot) {
88 ends.push(dots);
89 }
90 }
91 }
92 let dots = run(at, dot);
93 if dots > at {
94 for stop in at + 1..=dots {
95 ends.push(stop);
96 for number in stop + 1..=run(stop, digit) {
97 ends.push(number);
98 }
99 }
100 }
101 ends
102}
103
104fn accidental_ends(text: &[char], at: usize) -> Vec<usize> {
107 let mut ends = vec![at];
108 let bare = |from: usize| -> Vec<usize> {
109 match text.get(from) {
110 Some('n') => vec![from + 1],
111 Some(sign @ ('#' | '-')) => {
112 let run = text[from..].iter().take_while(|c| *c == sign).count();
113 (from + 1..=from + run).collect()
114 }
115 _ => Vec::new(),
116 }
117 };
118 ends.extend(bare(at));
119 if text.get(at) == Some(&'(') {
120 ends.extend(
121 bare(at + 1)
122 .into_iter()
123 .filter(|end| text.get(*end) == Some(&')'))
124 .map(|end| end + 1),
125 );
126 }
127 ends
128}
129
130fn name_ends(text: &[char]) -> Vec<usize> {
132 let Some(first) = text.first() else {
133 return Vec::new();
134 };
135 if ('A'..='G').contains(first) {
136 let run = text.iter().take_while(|c| *c == first).count();
137 return (1..=run)
138 .flat_map(|stop| accidental_ends(text, stop))
139 .collect();
140 }
141 if ('a'..='g').contains(first) {
142 let ticks = |from: usize| text[from..].iter().take_while(|c| **c == '\'').count();
143 let mut ends = Vec::new();
144 for after in accidental_ends(text, 1) {
145 for stop in after..=after + ticks(after) {
146 ends.push(stop);
147 }
148 }
149 for stop in 1..=1 + ticks(1) {
150 ends.extend(accidental_ends(text, stop));
151 }
152 return ends;
153 }
154 Vec::new()
155}
156
157fn is_note_token(text: &[char]) -> bool {
159 name_ends(text).into_iter().any(|name| {
160 duration_ends(text, name)
161 .into_iter()
162 .any(|end| modifiers_reach_end(text, end))
163 })
164}
165
166fn is_rest_body(text: &[char]) -> bool {
168 duration_ends(text, 0)
169 .into_iter()
170 .any(|end| modifiers_reach_end(text, end))
171}
172
173fn is_meter_token(text: &str) -> bool {
174 text.split_once('/').is_some_and(|(top, bottom)| {
175 !top.is_empty()
176 && !bottom.is_empty()
177 && top.chars().all(|c| c.is_ascii_digit())
178 && bottom.chars().all(|c| c.is_ascii_digit())
179 })
180}
181
182fn first_run(text: &str, test: impl Fn(char) -> bool) -> Option<&str> {
185 let start = text.find(&test)?;
186 let rest = &text[start..];
187 let end = rest.find(|c| !test(c)).unwrap_or(rest.len());
188 Some(&rest[..end])
189}
190
191fn without(text: &str, test: impl Fn(char) -> bool) -> String {
192 text.chars().filter(|c| !test(*c)).collect()
193}
194
195struct Reader {
197 elements: Vec<StreamElement>,
198 meter: Option<TimeSignature>,
199 last_length: FloatType,
200 states: Vec<State>,
201}
202
203fn with_duration(element: &mut StreamElement, change: impl FnOnce(&mut Duration)) {
204 match element {
205 StreamElement::Note(note) => {
206 let mut duration = note.duration().cloned().unwrap_or_else(Duration::quarter);
207 change(&mut duration);
208 note.set_duration(duration);
209 }
210 StreamElement::Rest(rest) => {
211 let mut duration = rest.duration().clone();
212 change(&mut duration);
213 rest.set_duration(duration);
214 }
215 _ => {}
216 }
217}
218
219fn written_type(duration: &Duration) -> DurationType {
221 match duration.components().as_slice() {
222 [(kind, _)] => *kind,
223 _ => DurationType::Quarter,
224 }
225}
226
227impl Reader {
228 fn duration_of(&mut self, text: &str) -> Result<Option<(Duration, bool)>> {
231 let mut duration = Duration::quarter();
232 let mut fermata = false;
233 let mut found = false;
234 if let Some(digits) = first_run(text, |c| c.is_ascii_digit()) {
235 found = true;
236 let number: u64 = digits.parse().map_err(|_| {
237 Error::Duration(format!("Cannot parse token with duration {digits}"))
238 })?;
239 if number == 0 {
240 if let Some(meter) = &self.meter {
241 duration = meter.bar_duration();
242 fermata = true;
243 }
244 } else {
245 let kind = (number <= 2048 && number.is_power_of_two())
246 .then(|| {
247 DurationType::ALL
248 .into_iter()
249 .find(|kind| kind.quarter_length() == 4.0 / number as FloatType)
250 })
251 .flatten();
252 let Some(kind) = kind else {
254 return Ok(None);
255 };
256 duration = Duration::from_type(kind);
257 }
258 }
259 if let Some(dots) = first_run(text, |c| c == '.') {
260 let kind = written_type(&duration);
261 duration = Duration::from_type_with_dots(kind, dots.len() as u32);
262 }
263 if !found {
264 duration = Duration::new(self.last_length)?;
265 }
266 self.last_length = duration.quarter_length();
267 Ok(Some((duration, fermata)))
268 }
269
270 fn note(&mut self, token: &str) -> Result<Option<StreamElement>> {
272 let mut text = token.to_string();
273 let mut step = 'C';
274 let mut octave: IntegerType = 4;
275 let low = |c: char| ('A'..='G').contains(&c);
276 if let Some(run) = first_run(&text, low) {
277 step = run.chars().next().unwrap_or('C');
278 octave = 4 - run.chars().count() as IntegerType;
279 text = without(&text, low);
280 }
281 let high = |c: char| ('a'..='g').contains(&c);
282 if let Some(start) = text.find(high) {
283 let letter = text[start..].chars().next().unwrap_or('c');
284 let ticks = text[start + 1..].chars().take_while(|c| *c == '\'').count();
285 step = letter.to_ascii_uppercase();
286 octave = 4 + ticks as IntegerType;
287 let mut kept = String::new();
289 let mut after_letter = false;
290 for character in text.chars() {
291 if high(character) {
292 after_letter = true;
293 } else if character == '\'' && after_letter {
294 } else {
295 after_letter = false;
296 kept.push(character);
297 }
298 }
299 text = kept;
300 }
301 let mut editorial = false;
303 let signs = |c: char| matches!(c, '#' | '-' | 'n');
304 let chars: Vec<char> = text.chars().collect();
305 let found = (0..chars.len()).find_map(|open| {
306 if chars[open] != '(' {
307 return None;
308 }
309 let run = chars[open + 1..].iter().take_while(|c| signs(**c)).count();
310 (run > 0 && chars.get(open + 1 + run) == Some(&')')).then_some((open, run))
311 });
312 if let Some((open, run)) = found {
313 editorial = true;
314 text = chars[open + 1..open + 1 + run]
315 .iter()
316 .chain(&chars[open + run + 2..])
317 .collect();
318 }
319 let mut alter: Option<IntegerType> = None;
320 for (sign, direction) in [('#', 1), ('-', -1), ('n', 0)] {
321 let is_sign = |c: char| c == sign;
322 if let Some(run) = first_run(&text, is_sign) {
323 let count = if sign == 'n' { 1 } else { run.len() };
324 alter = Some(direction * count as IntegerType);
325 text = without(&text, is_sign);
326 }
327 }
328 let mut pitch = PitchOptions::new().step(step).octave(octave).build()?;
329 if let Some(alter) = alter
330 && !editorial
331 {
332 pitch.set_written_accidental(Some(Accidental::new(alter)?));
333 }
334 let Some((duration, fermata)) = self.duration_of(&text)? else {
335 return Ok(None);
336 };
337 let mut note = Note::from_pitch(pitch).with_duration(duration);
338 if fermata {
339 note.expressions_mut()
340 .push(Expression::Fermata(Fermata::new()));
341 }
342 Ok(Some(note.into()))
343 }
344
345 fn rest(&mut self, body: &str) -> Result<Option<StreamElement>> {
346 let Some((duration, fermata)) = self.duration_of(body)? else {
347 return Ok(None);
348 };
349 let mut rest = Rest::new(duration);
350 if fermata {
351 rest.expressions_mut()
352 .push(Expression::Fermata(Fermata::new()));
353 }
354 Ok(Some(rest.into()))
355 }
356
357 fn end(&mut self, state: State) -> Result<()> {
359 match state {
360 State::Tie { tokens } => {
361 let opened = |tie: Option<&Tie>| match tie.cloned() {
362 None => Tie::new(TieType::Start),
363 Some(mut tie) => {
364 tie.set_tie_type(TieType::Continue);
365 tie
366 }
367 };
368 match tokens.first().map(|index| &mut self.elements[*index]) {
369 Some(StreamElement::Note(first)) => {
370 let tie = opened(first.tie());
371 first.set_tie(Some(tie));
372 }
373 Some(StreamElement::Rest(first)) => {
374 let tie = opened(first.tie());
375 first.set_tie(Some(tie));
376 }
377 Some(_) => {
378 return Err(Error::Meter("a time signature cannot be tied".to_string()));
379 }
380 None => {}
381 }
382 match tokens.get(1).map(|index| &mut self.elements[*index]) {
383 Some(StreamElement::Note(second)) => {
384 second.set_tie(Some(Tie::new(TieType::Stop)));
385 }
386 Some(StreamElement::Rest(second)) => {
387 second.set_tie(Some(Tie::new(TieType::Stop)));
388 }
389 _ => {}
390 }
391 }
392 State::Tuplet { tokens, .. } => {
393 let mark = |element: &mut StreamElement, kind: TupletType| {
394 with_duration(element, |duration| {
395 let mut tuplets = duration.tuplets();
396 if let Some(first) = tuplets.first_mut() {
397 first.set_tuplet_type(Some(kind));
398 }
399 duration.set_tuplets(tuplets);
400 });
401 };
402 if let (Some(first), Some(last)) = (tokens.first(), tokens.last()) {
403 mark(&mut self.elements[*first], TupletType::Start);
404 mark(&mut self.elements[*last], TupletType::Stop);
405 }
406 }
407 }
408 Ok(())
409 }
410
411 fn token(&mut self, token: &str) -> Result<()> {
413 let mut text = token.to_string();
414
415 loop {
417 let chars: Vec<char> = text.chars().collect();
418 let Some(brace) = (1..chars.len()).find(|index| {
419 chars[*index] == '{'
420 && (chars[index - 1].is_alphanumeric() || chars[index - 1] == '_')
421 }) else {
422 break;
423 };
424 let start = (0..brace)
425 .rev()
426 .take_while(|index| chars[*index].is_alphanumeric() || chars[*index] == '_')
427 .last()
428 .unwrap_or(brace);
429 let word: String = chars[start..brace].iter().collect();
430 text = chars[..start].iter().chain(&chars[brace + 1..]).collect();
431 let ratio = match word.as_str() {
432 "trip" => Some((3, 2)),
433 "quad" => Some((4, 3)),
434 _ => None,
436 };
437 if let Some((actual, normal)) = ratio {
438 self.states.push(State::Tuplet {
439 actual,
440 normal,
441 tokens: Vec::new(),
442 });
443 }
444 }
445 if text.contains('~') {
446 text = text.replace('~', "");
447 self.states.push(State::Tie { tokens: Vec::new() });
448 }
449
450 let closing = text.matches('}').count();
451 text = text.replace('}', "");
452
453 let mut id: Option<String> = None;
455 if text.contains('=') {
456 let mut kept = String::new();
457 let mut rest = &text[..];
458 while let Some(at) = rest.find('=') {
459 kept.push_str(&rest[..at]);
460 let after = &rest[at + 1..];
461 let end = after
462 .find(|c: char| !c.is_ascii_alphanumeric())
463 .unwrap_or(after.len());
464 id.get_or_insert_with(|| after[..end].to_string());
465 rest = &after[end..];
466 }
467 kept.push_str(rest);
468 text = kept;
469 }
470 let mut lyric: Option<String> = None;
471 if let Some(start) = text.find('_') {
472 lyric = Some(text[start + 1..].to_string());
473 text.truncate(start);
474 }
475
476 let chars: Vec<char> = text.chars().collect();
477 let mut made: Option<StreamElement> = None;
478 if is_meter_token(&text) {
479 let meter = TimeSignature::from_ratio_string(&text)?;
480 self.meter = Some(meter.clone());
481 made = Some(meter.into());
482 } else if chars.first() == Some(&'r') && is_rest_body(&chars[1..]) {
483 made = self.rest(&text[1..])?;
484 }
485 if made.is_none() && is_note_token(&chars) {
486 made = self.note(&text)?;
487 }
488
489 if let Some(mut element) = made {
490 if let (Some(id), StreamElement::Note(note)) = (&id, &mut element) {
491 note.set_id(Some(id.clone()));
492 }
493 if let Some(lyric) = &lyric {
494 match &mut element {
495 StreamElement::Note(note) => note.set_lyric(Some(lyric))?,
496 StreamElement::Rest(rest) => rest.set_lyric(Some(lyric)),
497 _ => {}
498 }
499 }
500 let index = self.elements.len();
503 let counts = !matches!(element, StreamElement::TimeSignature(_));
504 self.elements.push(element);
505 if counts {
506 let mut place = 0;
507 while place < self.states.len() {
508 let expired = match &mut self.states[place] {
509 State::Tuplet {
510 actual,
511 normal,
512 tokens,
513 } => {
514 tokens.push(index);
515 let (actual, normal) = (*actual, *normal);
516 let element = &mut self.elements[index];
517 let values = element
518 .duration()
519 .map_or(1, |duration| duration.components().len());
520 if values != 1 {
521 return Err(Error::Duration("Unknown type: complex".to_string()));
522 }
523 with_duration(element, |duration| {
524 let kind = written_type(duration);
525 duration.append_tuplet(Tuplet::new(actual, normal, kind, 0));
526 });
527 false
528 }
529 State::Tie { tokens } => {
530 tokens.push(index);
531 tokens.len() == 2
532 }
533 };
534 if expired {
535 let state = self.states.remove(place);
536 self.end(state)?;
537 } else {
538 place += 1;
539 }
540 }
541 }
542 }
543
544 for _ in 0..closing.min(self.states.len()) {
546 let mut at = self.states.len() - 1;
547 while at > 0 && matches!(self.states[at], State::Tie { .. }) {
548 at -= 1;
549 }
550 let state = self.states.remove(at);
551 self.end(state)?;
552 }
553 Ok(())
554 }
555}
556
557pub fn from_tiny_notation(text: &str) -> Result<Stream> {
582 let text = text.trim_start();
583 let body = match text.split_once(':') {
584 Some((head, body)) if head.eq_ignore_ascii_case("tinynotation") => body,
585 _ => text,
586 };
587 let mut reader = Reader {
588 elements: Vec::new(),
589 meter: None,
590 last_length: 1.0,
591 states: Vec::new(),
592 };
593 for token in body.split_whitespace() {
594 reader.token(token)?;
595 }
596 let mut events = Vec::new();
597 let mut offset = 0.0;
598 for element in reader.elements {
599 let length = element.quarter_length();
600 events.push(StreamEvent::new(offset, element));
601 offset = op_frac(offset + length);
602 }
603 let mut part = Stream::from_events(events);
604 part.set_kind(StreamKind::Part);
605 make_measures(&part)
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611
612 #[test]
613 fn a_closing_brace_ends_the_bracket_rather_than_a_tie() {
614 let part = from_tiny_notation("4/4 trip{c8 d e~} e4 f2").unwrap();
615 let held: Vec<(FloatType, Option<TieType>, usize)> = part
616 .notes()
617 .into_iter()
618 .filter_map(|(_, element)| match element {
619 StreamElement::Note(note) => Some((
620 note.duration().map_or(0.0, Duration::quarter_length),
621 note.tie().map(|tie| tie.tie_type()),
622 note.duration()
623 .map_or(0, |duration| duration.tuplets().len()),
624 )),
625 _ => None,
626 })
627 .collect();
628 let third = 1.0 / 3.0;
629 assert_eq!(
630 held,
631 [
632 (third, None, 1),
633 (third, None, 1),
634 (third, Some(TieType::Start), 1),
635 (1.0, Some(TieType::Stop), 0),
636 (2.0, None, 0),
637 ]
638 );
639 }
640
641 #[test]
642 fn a_meter_passes_open_states_by() {
643 let part = from_tiny_notation("2/4 trip{c8 d 4/4} e4 f4 g2").unwrap();
644 let lengths: Vec<FloatType> = notes(&part).into_iter().map(|(_, length)| length).collect();
645 let third = 1.0 / 3.0;
646 assert_eq!(lengths, [third, third, 1.0, 1.0, 2.0]);
647 }
648
649 fn notes(part: &Stream) -> Vec<(String, FloatType)> {
650 part.notes()
651 .into_iter()
652 .filter_map(|(_, element)| match element {
653 StreamElement::Note(note) => Some((
654 note.pitch().name_with_octave(),
655 note.duration().map_or(0.0, Duration::quarter_length),
656 )),
657 _ => None,
658 })
659 .collect()
660 }
661
662 #[test]
663 fn letters_and_marks_say_the_octave() {
664 let part = from_tiny_notation("CC4 C c c' c''").unwrap();
666 let names: Vec<String> = notes(&part).into_iter().map(|(name, _)| name).collect();
667 assert_eq!(names, ["C2", "C3", "C4", "C5", "C6"]);
668 }
669
670 #[test]
671 fn a_length_lasts_until_the_next_one() {
672 let part = from_tiny_notation("4/4 c4 d8 e f#2 g-4. an16").unwrap();
673 assert_eq!(
674 notes(&part),
675 [
676 ("C4".to_string(), 1.0),
677 ("D4".to_string(), 0.5),
678 ("E4".to_string(), 0.5),
679 ("F#4".to_string(), 2.0),
680 ("Gb4".to_string(), 1.5),
681 ("A4".to_string(), 0.25),
682 ]
683 );
684 }
685
686 #[test]
687 fn a_triplet_is_bracketed_from_its_first_note_to_its_last() {
688 let part = from_tiny_notation("2/4 trip{c8 d e} f4").unwrap();
689 let measure = part.measures()[0];
690 let tuplets: Vec<Option<TupletType>> = measure
691 .events()
692 .iter()
693 .filter_map(|event| match event.element() {
694 StreamElement::Note(note) => note
695 .duration()
696 .map(|duration| duration.tuplets().first().and_then(Tuplet::tuplet_type)),
697 _ => None,
698 })
699 .collect();
700 assert_eq!(
701 tuplets,
702 [Some(TupletType::Start), None, Some(TupletType::Stop), None]
703 );
704 assert!((measure.end_offset() - 2.0).abs() < 1e-9);
705 }
706
707 #[test]
708 fn a_tie_joins_a_note_to_the_next() {
709 let part = from_tiny_notation("c2~ c~ c4 d").unwrap();
710 let ties: Vec<Option<TieType>> = part
711 .notes()
712 .into_iter()
713 .filter_map(|(_, element)| match element {
714 StreamElement::Note(note) => Some(note.tie().map(Tie::tie_type)),
715 _ => None,
716 })
717 .collect();
718 assert_eq!(
719 ties,
720 [
721 Some(TieType::Start),
722 Some(TieType::Continue),
723 Some(TieType::Stop),
724 None
725 ]
726 );
727 }
728
729 #[test]
730 fn what_is_no_token_is_passed_over() {
731 let part = from_tiny_notation("c4 hello d r4 3").unwrap();
732 assert_eq!(notes(&part).len(), 2);
733 }
734}