Skip to main content

music21/
lily.rs

1//! LilyPond, written as music21's `lily.translate` writes it.
2//!
3//! music21 builds a tree of LilyPond objects (`lily.lilyObjects`) and writes
4//! it out, each object indented by how many objects stand above it -- which
5//! depends on the order the tree was put together in, since an object takes
6//! the parent it was last given as an attribute, or the first list it was
7//! put in, and none when it is appended to a list afterwards. The tree is
8//! kept here as music21 builds it, parents and all, so the text comes out
9//! as music21's does, space for space.
10//!
11//! ```
12//! use music21::lily::{LilyOptions, to_lilypond};
13//! use music21::tinynotation::from_tiny_notation;
14//!
15//! let line = from_tiny_notation("4/4 c4 d8 e f#4. g8")?;
16//! let text = to_lilypond(&line, &LilyOptions::default())?;
17//! assert!(text.starts_with("\\version \"2.24\""));
18//! assert!(text.contains("fis' 4."));
19//! # Ok::<(), music21::Error>(())
20//! ```
21
22use crate::bar::BarlineType;
23use crate::chord::Chord;
24use crate::clef::Clef;
25use crate::defaults::FloatType;
26use crate::duration::Duration;
27use crate::error::{Error, Result};
28use crate::expressions::Expression;
29use crate::key::Key;
30use crate::metadata::Metadata;
31use crate::meter::TimeSignature;
32use crate::notation::{Beams, StemDirection, Syllabic, TieType};
33use crate::note::Note;
34use crate::pitch::Pitch;
35use crate::rest::Rest;
36use crate::stream::{Stream, StreamElement, StreamKind};
37use crate::tempo::MetronomeMark;
38
39/// How a LilyPond file is written.
40#[derive(Clone, Debug, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42#[non_exhaustive]
43pub struct LilyOptions {
44    /// The LilyPond version the file says it is for, major and minor:
45    /// music21 asks the LilyPond it finds installed.
46    pub version: (u32, u32),
47}
48
49impl Default for LilyOptions {
50    fn default() -> Self {
51        Self { version: (2, 24) }
52    }
53}
54
55fn lily_error(message: impl Into<String>) -> Error {
56    Error::Notation(message.into())
57}
58
59// ------------------------------------------------------------------ tree
60
61/// A child of a LilyPond object: another object, or text as it stands.
62#[derive(Clone, Debug)]
63enum Item {
64    Node(usize),
65    Text(String),
66}
67
68/// The kinds of music21's LilyPond objects this writer makes.
69#[derive(Clone, Debug)]
70enum Kind {
71    Top {
72        contents: Vec<Item>,
73    },
74    Header {
75        body: Option<usize>,
76    },
77    HeaderBody {
78        assignments: Vec<Item>,
79    },
80    Assignment {
81        id: String,
82        value: String,
83    },
84    Embedded(String),
85    ScoreBlock {
86        body: usize,
87    },
88    ScoreBody {
89        music: usize,
90    },
91    OutputDef,
92    Layout,
93    MusicList {
94        contents: Vec<Item>,
95    },
96    Music {
97        composite: usize,
98    },
99    Sequential {
100        list: usize,
101        start_staff: bool,
102    },
103    Simultaneous {
104        list: usize,
105    },
106    SimpleMusic {
107        chord: usize,
108    },
109    ContextModification {
110        list: Vec<String>,
111    },
112    Composite {
113        prefix: Option<usize>,
114        grouped: Option<usize>,
115        lyrics: Option<usize>,
116    },
117    Grouped {
118        simultaneous: Option<usize>,
119        sequential: Option<usize>,
120    },
121    OptionalId(String),
122    Prefix {
123        kind: &'static str,
124        context: String,
125        id: Option<usize>,
126        modification: Option<usize>,
127        music: usize,
128        fraction: String,
129    },
130    NewLyrics {
131        lists: Vec<usize>,
132    },
133    PropertySet {
134        property: String,
135        value: String,
136    },
137    EventChord {
138        simple: Option<usize>,
139        post: Vec<String>,
140        note_chord: Option<usize>,
141    },
142    NoteChordElement {
143        body: usize,
144        duration: usize,
145        post: Vec<String>,
146    },
147    ChordBody {
148        elements: Vec<usize>,
149    },
150    ChordBodyElement {
151        parts: Vec<Item>,
152    },
153    Pitch {
154        name: String,
155        quotes: String,
156    },
157    Duration {
158        number: String,
159        dots: u32,
160    },
161    SimpleElement {
162        parts: Vec<Item>,
163    },
164    LyricElement(String),
165}
166
167#[derive(Clone, Debug)]
168struct Node {
169    parent: Option<usize>,
170    kind: Kind,
171}
172
173#[derive(Default)]
174struct Tree {
175    nodes: Vec<Node>,
176}
177
178impl Tree {
179    /// A new object, its object attributes taking it as their parent and
180    /// those in its lists taking it where they have none yet: music21's
181    /// `__setattr__` as `__init__` sets each attribute.
182    fn add(&mut self, kind: Kind) -> usize {
183        let index = self.nodes.len();
184        let (attributes, listed) = children(&kind);
185        self.nodes.push(Node { parent: None, kind });
186        for child in attributes {
187            self.nodes[child].parent = Some(index);
188        }
189        for child in listed {
190            self.adopt(index, child);
191        }
192        index
193    }
194
195    /// An attribute set to an object: it takes this parent, whatever it had.
196    fn set_parent(&mut self, child: usize, parent: usize) {
197        self.nodes[child].parent = Some(parent);
198    }
199
200    /// A list set with an object in it: it takes this parent only where it
201    /// has none.
202    fn adopt(&mut self, parent: usize, child: usize) {
203        if self.nodes[child].parent.is_none() {
204            self.nodes[child].parent = Some(parent);
205        }
206    }
207
208    fn contents_mut(&mut self, node: usize) -> Result<&mut Vec<Item>> {
209        match &mut self.nodes[node].kind {
210            Kind::MusicList { contents } | Kind::Top { contents } => Ok(contents),
211            _ => Err(lily_error(
212                "Cannot get a currentMusicList from contextObject",
213            )),
214        }
215    }
216
217    /// `newlineIndent`: a newline and a space for every object above.
218    fn newline_indent(&self, node: usize) -> String {
219        let mut depth = 0;
220        let mut current = self.nodes[node].parent;
221        while let Some(parent) = current {
222            depth += 1;
223            current = self.nodes[parent].parent;
224        }
225        format!("\n{}", " ".repeat(depth))
226    }
227
228    /// `str()`: the object written out, a blank line closed up.
229    fn str_of(&self, node: usize) -> String {
230        self.string_output(node).replace("\n\n", "\n")
231    }
232
233    fn item(&self, item: &Item) -> String {
234        match item {
235            Item::Node(node) => self.str_of(*node),
236            Item::Text(text) => text.clone(),
237        }
238    }
239
240    /// `newlineSeparateStringOutputIfNotNone`.
241    fn newline_separated(&self, node: usize, items: &[Item]) -> String {
242        let indent = self.newline_indent(node);
243        items.iter().map(|item| self.item(item) + &indent).collect()
244    }
245
246    /// `encloseCurly` of one object.
247    fn enclose(&self, node: usize, inner: Option<String>) -> String {
248        let Some(inner) = inner else {
249            return " { } ".to_string();
250        };
251        let indent = self.newline_indent(node);
252        format!(" {{ {indent}{inner}{indent} }} {indent}")
253    }
254
255    fn string_output(&self, node: usize) -> String {
256        match &self.nodes[node].kind {
257            Kind::Top { contents } => self.newline_separated(node, contents),
258            Kind::Header { body } => {
259                format!(
260                    "\\header{}",
261                    self.enclose(node, body.map(|body| self.str_of(body)))
262                )
263            }
264            Kind::HeaderBody { assignments } => self.newline_separated(node, assignments),
265            Kind::Assignment { id, value } => [id.as_str(), "=", &quote(value), " "].join(" "),
266            Kind::Embedded(content) => content.clone(),
267            Kind::ScoreBlock { body } => {
268                format!("\\score {}", self.enclose(node, Some(self.str_of(*body))))
269            }
270            Kind::ScoreBody { music } | Kind::Music { composite: music } => {
271                self.string_output(*music)
272            }
273            Kind::OutputDef => "\\paper { }".to_string(),
274            Kind::Layout => {
275                let lines = [
276                    "\\layout {",
277                    " \\context {",
278                    "   \\RemoveEmptyStaves",
279                    "   \\override VerticalAxisGroup.remove-first = ##t",
280                    " }",
281                    "}",
282                ];
283                let indent = self.newline_indent(node);
284                lines.iter().map(|line| format!("{line}{indent}")).collect()
285            }
286            Kind::MusicList { contents } => self.newline_separated(node, contents),
287            Kind::Sequential { list, start_staff } => format!(
288                "{{ {}{} }} {}",
289                if *start_staff { "\\startStaff " } else { "" },
290                self.string_output(*list),
291                self.newline_indent(node)
292            ),
293            Kind::Simultaneous { list } => {
294                let indent = self.newline_indent(node);
295                format!("{indent}<< {} >>{indent}", self.string_output(*list))
296            }
297            Kind::SimpleMusic { chord } => self.string_output(*chord),
298            Kind::ContextModification { list } => {
299                let indent = self.newline_indent(node);
300                format!(
301                    "\\with  {{ {indent}{}{indent} }} {indent}",
302                    list.join(&indent)
303                )
304            }
305            Kind::Composite {
306                prefix,
307                grouped,
308                lyrics,
309            } => {
310                let lyrics = lyrics.map(|lyrics| self.str_of(lyrics)).unwrap_or_default();
311                let music = prefix.or(*grouped).expect("composite music holds music");
312                format!("{}\n{lyrics}", self.str_of(music))
313            }
314            Kind::Grouped {
315                simultaneous,
316                sequential,
317            } => self.str_of(
318                simultaneous
319                    .or(*sequential)
320                    .expect("grouped music holds music"),
321            ),
322            Kind::OptionalId(content) => format!(" = {content}"),
323            Kind::Prefix {
324                kind,
325                context,
326                id,
327                modification,
328                music,
329                fraction,
330            } => {
331                if *kind == "tuplet" {
332                    return format!("\\tuplet {fraction} {} ", self.str_of(*music));
333                }
334                let mut out = format!("\\{kind} {context} ");
335                if let Some(id) = id {
336                    out += &(self.str_of(*id) + " ");
337                }
338                if let Some(modification) = modification {
339                    out += &(self.str_of(*modification) + " ");
340                }
341                out + &self.str_of(*music) + " "
342            }
343            Kind::NewLyrics { lists } => lists
344                .iter()
345                .map(|list| format!("\\addlyrics {}", self.string_output(*list)))
346                .collect(),
347            Kind::PropertySet { property, value } => format!("\\set {property} = {value} "),
348            Kind::EventChord {
349                simple,
350                post,
351                note_chord,
352            } => {
353                if let Some(note_chord) = note_chord {
354                    return self.str_of(*note_chord) + " ";
355                }
356                let mut out = simple.map(|simple| self.str_of(simple)).unwrap_or_default();
357                for event in post {
358                    out += &format!(" {event}");
359                }
360                out + " "
361            }
362            Kind::NoteChordElement {
363                body,
364                duration,
365                post,
366            } => {
367                let mut out = self.str_of(*body);
368                out += &(self.str_of(*duration) + " ");
369                for event in post {
370                    out += &format!("{event} ");
371                }
372                out
373            }
374            Kind::ChordBody { elements } => {
375                let inner: Vec<String> = elements
376                    .iter()
377                    .map(|element| self.str_of(*element))
378                    .collect();
379                ["<", &inner.join(" "), "> "].join(" ")
380            }
381            Kind::ChordBodyElement { parts } => parts
382                .iter()
383                .map(|part| self.item(part))
384                .collect::<Vec<_>>()
385                .join(" "),
386            Kind::Pitch { name, quotes } => format!("{name}{quotes} "),
387            Kind::Duration { number, dots } => {
388                format!("{number}{} ", ".".repeat(*dots as usize))
389            }
390            Kind::SimpleElement { parts } => parts.iter().map(|part| self.item(part)).collect(),
391            Kind::LyricElement(text) => format!("{text} "),
392        }
393    }
394}
395
396/// The objects a kind holds as attributes, and those it holds in lists.
397fn children(kind: &Kind) -> (Vec<usize>, Vec<usize>) {
398    let nodes = |items: &[Item]| -> Vec<usize> {
399        items
400            .iter()
401            .filter_map(|item| match item {
402                Item::Node(node) => Some(*node),
403                Item::Text(_) => None,
404            })
405            .collect()
406    };
407    match kind {
408        Kind::Top { contents } | Kind::MusicList { contents } => (Vec::new(), nodes(contents)),
409        Kind::HeaderBody { assignments } => (Vec::new(), nodes(assignments)),
410        Kind::Header { body } => (body.iter().copied().collect(), Vec::new()),
411        Kind::ScoreBlock { body } => (vec![*body], Vec::new()),
412        Kind::ScoreBody { music } | Kind::Music { composite: music } => (vec![*music], Vec::new()),
413        Kind::Sequential { list, .. } | Kind::Simultaneous { list } => (vec![*list], Vec::new()),
414        Kind::SimpleMusic { chord } => (vec![*chord], Vec::new()),
415        Kind::Composite {
416            prefix,
417            grouped,
418            lyrics,
419        } => (
420            [*prefix, *grouped, *lyrics].into_iter().flatten().collect(),
421            Vec::new(),
422        ),
423        Kind::Grouped {
424            simultaneous,
425            sequential,
426        } => (
427            [*simultaneous, *sequential].into_iter().flatten().collect(),
428            Vec::new(),
429        ),
430        Kind::Prefix {
431            id,
432            modification,
433            music,
434            ..
435        } => (
436            [*id, *modification, Some(*music)]
437                .into_iter()
438                .flatten()
439                .collect(),
440            Vec::new(),
441        ),
442        Kind::NewLyrics { lists } => (Vec::new(), lists.clone()),
443        Kind::EventChord {
444            simple, note_chord, ..
445        } => (
446            [*simple, *note_chord].into_iter().flatten().collect(),
447            Vec::new(),
448        ),
449        Kind::NoteChordElement { body, duration, .. } => (vec![*body, *duration], Vec::new()),
450        Kind::ChordBody { elements } => (Vec::new(), elements.clone()),
451        Kind::ChordBodyElement { parts } | Kind::SimpleElement { parts } => {
452            (Vec::new(), nodes(parts))
453        }
454        _ => (Vec::new(), Vec::new()),
455    }
456}
457
458/// `quoteString`: in double quotes, a space after.
459fn quote(text: &str) -> String {
460    format!("{} ", string_literal(text))
461}
462
463/// Text as one LilyPond string: in double quotes, with each backslash and
464/// quote inside escaped, so nothing in it is read as LilyPond or Scheme.
465fn string_literal(text: &str) -> String {
466    format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
467}
468
469/// `makeLettersOnlyId`: every character not a letter turned into one.
470fn letters_only(text: &str) -> String {
471    text.chars()
472        .map(|c| {
473            if c.is_alphabetic() {
474                c
475            } else {
476                char::from_u32(c as u32 % 26 + 97).unwrap_or('a')
477            }
478        })
479        .collect()
480}
481
482const COLOR_DEFINITION: &str = "color = #(define-music-function (color) (string?) #{\n        \\once \\override NoteHead.color = #(x11-color color)\n        \\once \\override Stem.color = #(x11-color color)\n        \\once \\override Rest.color = #(x11-color color)\n        \\once \\override Beam.color = #(x11-color color)\n     #})\n    ";
483const BOOK_HEADER: &str = "\\include \"lilypond-book-preamble.ly\"\n    ";
484
485// ------------------------------------------------------------ translator
486
487/// music21's `LilypondConverter`.
488struct Converter<'a> {
489    tree: Tree,
490    top: usize,
491    context: usize,
492    stored: Vec<usize>,
493    current_measure: Option<&'a Stream>,
494    in_word: bool,
495    /// The time signature last written, which a pickup with none of its
496    /// own is a part of.
497    meter: Option<TimeSignature>,
498    /// The number given to the next stream with no id of its own, where
499    /// music21 writes the address of the object.
500    unnamed: usize,
501}
502
503/// The id music21 writes for a stream: its own, or one made for it.
504fn stream_id(stream: &Stream, unnamed: &mut usize) -> String {
505    match stream.id() {
506        Some(id) => id.to_string(),
507        None => {
508            *unnamed += 1;
509            format!("{}", 1_000_000 + *unnamed)
510        }
511    }
512}
513
514fn is_part(stream: &Stream) -> bool {
515    matches!(stream.kind(), StreamKind::Part | StreamKind::PartStaff)
516}
517
518impl<'a> Converter<'a> {
519    fn new() -> Self {
520        let mut tree = Tree::default();
521        let top = tree.add(Kind::Top {
522            contents: Vec::new(),
523        });
524        Self {
525            tree,
526            top,
527            context: top,
528            stored: Vec::new(),
529            current_measure: None,
530            in_word: false,
531            meter: None,
532            unnamed: 0,
533        }
534    }
535
536    fn new_context(&mut self, context: usize) {
537        self.stored.push(self.context);
538        self.context = context;
539    }
540
541    fn restore_context(&mut self) {
542        self.context = self.stored.pop().unwrap_or(self.top);
543    }
544
545    /// Appends an object to the context's contents and gives it the context
546    /// as its parent, as music21 does after appending.
547    fn append_to_context(&mut self, node: usize) -> Result<()> {
548        let context = self.context;
549        self.tree.contents_mut(context)?.push(Item::Node(node));
550        self.tree.set_parent(node, context);
551        Ok(())
552    }
553
554    fn embedded(&mut self, content: impl Into<String>) -> usize {
555        self.tree.add(Kind::Embedded(content.into()))
556    }
557
558    /// `loadObjectFromScore`.
559    fn load_score(&mut self, score: &'a Stream, options: &LilyOptions) -> Result<()> {
560        let version = self.embedded(format!(
561            "\\version {}",
562            quote(&format!("{}.{}", options.version.0, options.version.1))
563        ));
564        let book = self.embedded(BOOK_HEADER);
565        let colors = self.embedded(COLOR_DEFINITION);
566        let header = self.tree.add(Kind::Header { body: None });
567        let score_block = self.score_block(score)?;
568        let output = self.tree.add(Kind::OutputDef);
569        let layout = self.tree.add(Kind::Layout);
570        let contents: Vec<Item> = [version, book, colors, header, score_block, output, layout]
571            .into_iter()
572            .map(Item::Node)
573            .collect();
574        if let Some(metadata) = score.metadata() {
575            self.set_header(metadata, header);
576        }
577        // The contents go to the context music21 is left in, which is the
578        // top unless a tuplet started and never stopped, or the other way.
579        let context = self.context;
580        if matches!(self.tree.nodes[context].kind, Kind::Composite { .. }) {
581            return Err(lily_error(
582                "a tuplet opened and never closed, or closed and never opened, leaves nowhere to write the score",
583            ));
584        }
585        for item in &contents {
586            if let Item::Node(node) = item {
587                self.tree.adopt(context, *node);
588            }
589        }
590        *self.tree.contents_mut(context)? = contents;
591        Ok(())
592    }
593
594    /// `setHeaderFromMetadata`: the best title and the alternative title.
595    fn set_header(&mut self, metadata: &Metadata, header: usize) {
596        let body = self.tree.add(Kind::HeaderBody {
597            assignments: Vec::new(),
598        });
599        if let Kind::Header { body: held } = &mut self.tree.nodes[header].kind {
600            *held = Some(body);
601        }
602        self.tree.set_parent(body, header);
603        let best = ["title", "popularTitle", "alternativeTitle", "movementName"]
604            .iter()
605            .find_map(|name| {
606                metadata
607                    .string_value(name)
608                    .filter(|title| !title.is_empty())
609            });
610        for (id, value) in [
611            ("title", best),
612            (
613                "subtitle",
614                metadata
615                    .string_value("alternativeTitle")
616                    .filter(|title| !title.is_empty()),
617            ),
618        ] {
619            let Some(value) = value else {
620                continue;
621            };
622            let assignment = self.tree.add(Kind::Assignment {
623                id: id.to_string(),
624                value,
625            });
626            if let Kind::HeaderBody { assignments } = &mut self.tree.nodes[body].kind {
627                assignments.push(Item::Node(assignment));
628            }
629            self.tree.set_parent(assignment, body);
630        }
631    }
632
633    /// `lyScoreBlockFromScore`.
634    fn score_block(&mut self, score: &'a Stream) -> Result<usize> {
635        let composite = self.tree.add(Kind::Composite {
636            prefix: None,
637            grouped: None,
638            lyrics: None,
639        });
640        self.new_context(composite);
641        let parts: Vec<&'a Stream> = score
642            .events()
643            .iter()
644            .filter_map(|event| match event.element() {
645                StreamElement::Stream(inner) if is_part(inner) => Some(&**inner),
646                _ => None,
647            })
648            .collect();
649        if parts.is_empty() {
650            let prefix = self.prefix_from_stream(score, None, None, false)?;
651            if let Kind::Composite { prefix: held, .. } = &mut self.tree.nodes[composite].kind {
652                *held = Some(prefix);
653            }
654            self.tree.set_parent(prefix, composite);
655        } else {
656            let grouped = self.grouped_from_parts(&parts)?;
657            if let Kind::Composite { grouped: held, .. } = &mut self.tree.nodes[composite].kind {
658                *held = Some(grouped);
659            }
660            self.tree.set_parent(grouped, composite);
661        }
662        let music = self.tree.add(Kind::Music { composite });
663        let body = self.tree.add(Kind::ScoreBody { music });
664        let block = self.tree.add(Kind::ScoreBlock { body });
665        self.restore_context();
666        Ok(block)
667    }
668
669    /// `lyGroupedMusicListFromScoreWithParts`.
670    fn grouped_from_parts(&mut self, parts: &[&'a Stream]) -> Result<usize> {
671        let grouped = self.tree.add(Kind::Grouped {
672            simultaneous: None,
673            sequential: None,
674        });
675        let list = self.tree.add(Kind::MusicList {
676            contents: Vec::new(),
677        });
678        let simultaneous = self.tree.add(Kind::Simultaneous { list });
679        if let Kind::Grouped {
680            simultaneous: held, ..
681        } = &mut self.tree.nodes[grouped].kind
682        {
683            *held = Some(simultaneous);
684        }
685        self.tree.set_parent(simultaneous, grouped);
686        self.new_context(list);
687        let mut composites = Vec::new();
688        for part in parts {
689            composites.push(self.prefix_from_stream(part, None, None, false)?);
690        }
691        self.restore_context();
692        for composite in &composites {
693            self.tree.adopt(list, *composite);
694        }
695        if let Kind::MusicList { contents } = &mut self.tree.nodes[list].kind {
696            *contents = composites.into_iter().map(Item::Node).collect();
697        }
698        Ok(grouped)
699    }
700
701    /// `lyPrefixCompositeMusicFromStream`.
702    fn prefix_from_stream(
703        &mut self,
704        stream: &'a Stream,
705        context_type: Option<&str>,
706        kind: Option<&'static str>,
707        start_staff: bool,
708    ) -> Result<usize> {
709        let id_text = stream_id(stream, &mut self.unnamed);
710        let (context, id) = match context_type {
711            Some(context) => (
712                context.to_string(),
713                Some(self.tree.add(Kind::OptionalId(letters_only(&id_text)))),
714            ),
715            None if is_part(stream) => (
716                "Staff".to_string(),
717                Some(self.tree.add(Kind::OptionalId(letters_only(&id_text)))),
718            ),
719            None => ("Voice".to_string(), None),
720        };
721        let mut modifications = Vec::new();
722        if crate::makenotation::beams_made(stream) {
723            modifications.push("\\autoBeamOff ".to_string());
724        }
725        let lines = staff_lines(stream);
726        if lines != 5 {
727            modifications.push(format!("\\override StaffSymbol.line-count = #{lines}"));
728        }
729        let lyrics = self.new_lyrics(stream, &letters_only(&id_text));
730        let sequential = self.sequential_from_stream(stream, start_staff)?;
731        let grouped = self.tree.add(Kind::Grouped {
732            simultaneous: None,
733            sequential: Some(sequential),
734        });
735        let composite = self.tree.add(Kind::Composite {
736            prefix: None,
737            grouped: Some(grouped),
738            lyrics: Some(lyrics),
739        });
740        let music = self.tree.add(Kind::Music { composite });
741        let modification = (!modifications.is_empty()).then(|| {
742            self.tree.add(Kind::ContextModification {
743                list: modifications,
744            })
745        });
746        Ok(self.tree.add(Kind::Prefix {
747            kind: kind.unwrap_or("new"),
748            context,
749            id,
750            modification,
751            music,
752            fraction: String::new(),
753        }))
754    }
755
756    /// `lyNewLyricsFromStream`.
757    fn new_lyrics(&mut self, stream: &Stream, id: &str) -> usize {
758        let aligned = format!("#{}", quote(id));
759        let mut lists = Vec::new();
760        for (_, lyrics) in stream_lyrics(stream) {
761            let mut items = vec![Item::Node(self.tree.add(Kind::PropertySet {
762                property: "alignBelowContext".to_string(),
763                value: aligned.clone(),
764            }))];
765            self.in_word = false;
766            for lyric in lyrics {
767                let text = self.lyric_text(lyric);
768                items.push(Item::Node(self.tree.add(Kind::LyricElement(text))));
769            }
770            self.in_word = false;
771            let list = self.tree.add(Kind::MusicList { contents: items });
772            let sequential = self.tree.add(Kind::Sequential {
773                list,
774                start_staff: false,
775            });
776            lists.push(self.tree.add(Kind::Grouped {
777                simultaneous: None,
778                sequential: Some(sequential),
779            }));
780        }
781        self.tree.add(Kind::NewLyrics { lists })
782    }
783
784    /// `lyLyricElementFromM21Lyric`.
785    fn lyric_text(&mut self, lyric: Option<&crate::notation::Lyric>) -> String {
786        let mut in_word = self.in_word;
787        let text = match lyric {
788            None => " _ ".to_string(),
789            Some(lyric) if lyric.text().is_empty() => " _ ".to_string(),
790            Some(lyric) => {
791                let mut text = string_literal(&lyric.text());
792                match lyric.syllabic() {
793                    Syllabic::End => {
794                        text += "__";
795                        in_word = false;
796                    }
797                    Syllabic::Begin | Syllabic::Middle => {
798                        text += " --";
799                        in_word = true;
800                    }
801                    _ => {}
802                }
803                text
804            }
805        };
806        self.in_word = in_word;
807        text
808    }
809
810    /// `lySequentialMusicFromStream`.
811    fn sequential_from_stream(&mut self, stream: &'a Stream, start_staff: bool) -> Result<usize> {
812        let list = self.tree.add(Kind::MusicList {
813            contents: Vec::new(),
814        });
815        let sequential = self.tree.add(Kind::Sequential { list, start_staff });
816        self.new_context(list);
817        self.append_stream(stream)?;
818        if let Some(bar) = self.close_measure() {
819            // Appended, so it takes no parent.
820            self.tree.contents_mut(list)?.push(Item::Node(bar));
821        }
822        self.restore_context();
823        Ok(sequential)
824    }
825
826    /// `appendObjectsToContextFromStream`: the stream's elements a group of
827    /// those at one offset at a time, voices together at once.
828    fn append_stream(&mut self, stream: &'a Stream) -> Result<()> {
829        let events = stream.events();
830        let mut start = 0;
831        while start < events.len() {
832            let offset = events[start].offset();
833            let mut end = start + 1;
834            while end < events.len() && events[end].offset() == offset {
835                end += 1;
836            }
837            let group = &events[start..end];
838            if group.len() == 1 {
839                self.append_element(group[0].element())?;
840            } else {
841                let voices: Vec<&'a Stream> = group
842                    .iter()
843                    .filter_map(|event| match event.element() {
844                        StreamElement::Stream(inner) if inner.kind() == StreamKind::Voice => {
845                            Some(&**inner)
846                        }
847                        _ => None,
848                    })
849                    .collect();
850                if !voices.is_empty() {
851                    let grouped = self.tree.add(Kind::Grouped {
852                        simultaneous: None,
853                        sequential: None,
854                    });
855                    let list = self.tree.add(Kind::MusicList {
856                        contents: Vec::new(),
857                    });
858                    let simultaneous = self.tree.add(Kind::Simultaneous { list });
859                    if let Kind::Grouped {
860                        simultaneous: held, ..
861                    } = &mut self.tree.nodes[grouped].kind
862                    {
863                        *held = Some(simultaneous);
864                    }
865                    self.tree.set_parent(simultaneous, grouped);
866                    let mut composites = Vec::new();
867                    for voice in voices {
868                        composites.push(self.prefix_from_stream(voice, None, None, false)?);
869                    }
870                    for composite in &composites {
871                        self.tree.adopt(list, *composite);
872                    }
873                    if let Kind::MusicList { contents } = &mut self.tree.nodes[list].kind {
874                        *contents = composites.into_iter().map(Item::Node).collect();
875                    }
876                    self.append_to_context(grouped)?;
877                }
878                for event in group {
879                    if !matches!(event.element(), StreamElement::Stream(inner) if inner.kind() == StreamKind::Voice)
880                    {
881                        self.append_element(event.element())?;
882                    }
883                }
884            }
885            start = end;
886        }
887        Ok(())
888    }
889
890    /// `appendM21ObjectToContext`.
891    fn append_element(&mut self, element: &'a StreamElement) -> Result<()> {
892        if !matches!(element, StreamElement::Stream(_))
893            && let Some(duration) = element.duration()
894            && duration.is_complex()
895        {
896            for (_, piece) in crate::makenotation::element_at_durations(element, duration)? {
897                self.append_owned(piece)?;
898            }
899            return Ok(());
900        }
901        match element {
902            StreamElement::Stream(measure) if measure.kind() == StreamKind::Measure => {
903                if let Some(bar) = self.close_measure() {
904                    self.append_to_context(bar)?;
905                }
906                if let Some(pad) = self.padding(measure)? {
907                    self.append_to_context(pad)?;
908                }
909                self.append_stream(measure)?;
910                self.current_measure = Some(measure);
911            }
912            StreamElement::Stream(inner) => {
913                let prefix = self.prefix_from_stream(inner, None, None, false)?;
914                self.append_to_context(prefix)?;
915            }
916            other => self.append_owned(other.clone())?,
917        }
918        Ok(())
919    }
920
921    /// `appendM21ObjectToContext` for what is not a stream.
922    fn append_owned(&mut self, element: StreamElement) -> Result<()> {
923        match &element {
924            StreamElement::Note(note) => self.append_note(note)?,
925            StreamElement::Rest(rest) => self.append_rest(rest)?,
926            StreamElement::Chord(chord) => self.append_chord(chord, chord.duration())?,
927            // music21's chord symbol is a chord, with no length to write.
928            StreamElement::ChordSymbol(symbol) => {
929                let chord = Chord::new(symbol.pitches()?)?;
930                self.append_chord(&chord, Some(symbol.duration()))?;
931            }
932            StreamElement::Clef(clef) => {
933                let scheme = self.embedded(clef_scheme(clef));
934                self.append_to_context(scheme)?;
935            }
936            StreamElement::KeySignature(signature) => {
937                let scheme = self.embedded(key_scheme(&signature.as_key("major")));
938                self.append_to_context(scheme)?;
939            }
940            StreamElement::Key(key) => {
941                let scheme = self.embedded(key_scheme(key));
942                self.append_to_context(scheme)?;
943            }
944            StreamElement::TimeSignature(meter) => {
945                self.meter = Some(meter.clone());
946                let scheme = self.embedded(time_scheme(meter));
947                self.append_to_context(scheme)?;
948            }
949            StreamElement::MetronomeMark(mark) => {
950                if let Some(scheme) = tempo_scheme(mark)? {
951                    let scheme = self.embedded(scheme);
952                    self.append_to_context(scheme)?;
953                }
954            }
955            StreamElement::Layout(crate::layout::Layout::System(_)) => {
956                let scheme = self.embedded("\\break");
957                self.append_to_context(scheme)?;
958            }
959            StreamElement::Layout(crate::layout::Layout::Page(_)) => {
960                let scheme = self.embedded("\\pageBreak");
961                self.append_to_context(scheme)?;
962            }
963            _ => {}
964        }
965        Ok(())
966    }
967
968    /// `setContextForTupletStart`: a `\tuplet` the notes go into, where a
969    /// tuplet's bracket starts.
970    fn tuplet_start(&mut self, duration: Option<&Duration>) -> Result<()> {
971        let Some(first) = duration.and_then(|duration| duration.tuplets().into_iter().next())
972        else {
973            return Ok(());
974        };
975        if first.tuplet_type() != Some(crate::duration::TupletType::Start) {
976            return Ok(());
977        }
978        let fraction = format!("{}/{}", first.tuplet_actual().0, first.tuplet_normal().0);
979        let list = self.tree.add(Kind::MusicList {
980            contents: Vec::new(),
981        });
982        let sequential = self.tree.add(Kind::Sequential {
983            list,
984            start_staff: false,
985        });
986        let prefix = self.tree.add(Kind::Prefix {
987            kind: "tuplet",
988            context: String::new(),
989            id: None,
990            modification: None,
991            music: sequential,
992            fraction,
993        });
994        self.append_to_context(prefix)?;
995        self.new_context(list);
996        Ok(())
997    }
998
999    /// `setContextForTupletStop`.
1000    fn tuplet_stop(&mut self, duration: Option<&Duration>) {
1001        if duration
1002            .and_then(|duration| duration.tuplets().into_iter().next())
1003            .is_some_and(|first| first.tuplet_type() == Some(crate::duration::TupletType::Stop))
1004        {
1005            self.restore_context();
1006        }
1007    }
1008
1009    /// `appendBeamCode`: how many beams leave the stem on each side.
1010    fn beam_code(&mut self, beams: &Beams) -> Result<()> {
1011        let (mut left, mut right) = (0, 0);
1012        for beam in beams.beams() {
1013            match beam.beam_type().map_or("", |kind| kind.as_str()) {
1014                "start" => right += 1,
1015                "continue" => {
1016                    right += 1;
1017                    left += 1;
1018                }
1019                "stop" => left += 1,
1020                "partial" => {
1021                    if beam
1022                        .direction()
1023                        .is_some_and(|direction| direction.as_str() == "left")
1024                    {
1025                        left += 1;
1026                    } else {
1027                        right += 1;
1028                    }
1029                }
1030                _ => {}
1031            }
1032        }
1033        if left > 0 {
1034            let scheme = self.embedded(format!("\\set stemLeftBeamCount = #{left}"));
1035            self.append_to_context(scheme)?;
1036        }
1037        if right > 0 {
1038            let scheme = self.embedded(format!("\\set stemRightBeamCount = #{right}"));
1039            self.append_to_context(scheme)?;
1040        }
1041        Ok(())
1042    }
1043
1044    /// `appendStemCode`: a stem said to go up or down.
1045    fn stem_code(&mut self, direction: StemDirection) -> Result<()> {
1046        let said = match direction {
1047            StemDirection::Up => "UP",
1048            StemDirection::Down => "DOWN",
1049            _ => return Ok(()),
1050        };
1051        let scheme = self.embedded(format!("\\once \\override Stem.direction = #{said} "));
1052        self.append_to_context(scheme)
1053    }
1054
1055    /// `appendContextFromNoteOrRest` for a note.
1056    fn append_note(&mut self, note: &Note) -> Result<()> {
1057        let duration = note.duration();
1058        if duration.is_some_and(Duration::is_grace) {
1059            return Ok(());
1060        }
1061        self.tuplet_start(duration)?;
1062        self.beam_code(note.beams())?;
1063        self.stem_code(note.stem_direction())?;
1064        let mut parts = Vec::new();
1065        if let Some(color) = note.color().filter(|color| !color.is_empty())
1066            && !note.hidden()
1067        {
1068            let color = string_literal(color);
1069            parts.push(Item::Text(format!("\\override NoteHead.color = {color}\n")));
1070            parts.push(Item::Text(format!("\\override Stem.color = {color}\n")));
1071        }
1072        if note.hidden() {
1073            parts.push(Item::Text("s ".to_string()));
1074        } else {
1075            parts.push(Item::Node(self.pitch_node(note.pitch())?));
1076            parts.extend(accidental_marks(note.pitch()));
1077        }
1078        let length = self.duration_node(&element_duration(duration))?;
1079        parts.push(Item::Node(length));
1080        if let Some(first) = note.beams().beams().first() {
1081            match first.beam_type().map_or("", |kind| kind.as_str()) {
1082                "start" => parts.push(Item::Text("[ ".to_string())),
1083                "stop" => parts.push(Item::Text("] ".to_string())),
1084                _ => {}
1085            }
1086        }
1087        let post = post_events(note.tie().map(|tie| tie.tie_type()), note.expressions());
1088        self.push_simple(parts, post)?;
1089        self.tuplet_stop(duration);
1090        Ok(())
1091    }
1092
1093    /// `appendContextFromNoteOrRest` for a rest.
1094    fn append_rest(&mut self, rest: &Rest) -> Result<()> {
1095        let duration = Some(rest.duration());
1096        if rest.duration().is_grace() {
1097            return Ok(());
1098        }
1099        self.tuplet_start(duration)?;
1100        let mut parts = vec![Item::Text(
1101            if rest.hidden() { "s " } else { "r " }.to_string(),
1102        )];
1103        let length = self.duration_node(rest.duration())?;
1104        parts.push(Item::Node(length));
1105        let post = post_events(rest.tie().map(|tie| tie.tie_type()), rest.expressions());
1106        self.push_simple(parts, post)?;
1107        self.tuplet_stop(duration);
1108        Ok(())
1109    }
1110
1111    fn push_simple(&mut self, parts: Vec<Item>, post: Vec<String>) -> Result<()> {
1112        let simple = self.tree.add(Kind::SimpleElement { parts });
1113        let chord = self.tree.add(Kind::EventChord {
1114            simple: Some(simple),
1115            post,
1116            note_chord: None,
1117        });
1118        let music = self.tree.add(Kind::SimpleMusic { chord });
1119        self.append_to_context(music)
1120    }
1121
1122    /// `appendContextFromChord`, whose beams and stem music21 writes twice:
1123    /// once before the chord and once as it makes the chord.
1124    fn append_chord(&mut self, chord: &Chord, duration: Option<&Duration>) -> Result<()> {
1125        self.tuplet_start(duration)?;
1126        self.beam_code(chord.beams())?;
1127        self.stem_code(chord.stem_direction())?;
1128        // `lySimpleMusicFromChord`.
1129        self.beam_code(chord.beams())?;
1130        self.stem_code(chord.stem_direction())?;
1131        let mut elements = Vec::new();
1132        for note in chord.notes() {
1133            let mut parts = vec![Item::Node(self.pitch_node(note.pitch())?)];
1134            parts.extend(accidental_marks(note.pitch()));
1135            elements.push(self.tree.add(Kind::ChordBodyElement { parts }));
1136        }
1137        let body = self.tree.add(Kind::ChordBody { elements });
1138        let length = self.duration_node(&element_duration(duration))?;
1139        let post = post_events(chord.tie().map(|tie| tie.tie_type()), chord.expressions());
1140        let note_chord = self.tree.add(Kind::NoteChordElement {
1141            body,
1142            duration: length,
1143            post,
1144        });
1145        let event = self.tree.add(Kind::EventChord {
1146            simple: None,
1147            post: Vec::new(),
1148            note_chord: Some(note_chord),
1149        });
1150        let music = self.tree.add(Kind::SimpleMusic { chord: event });
1151        self.append_to_context(music)?;
1152        self.tuplet_stop(duration);
1153        Ok(())
1154    }
1155
1156    /// `lyPitchFromPitch`.
1157    fn pitch_node(&mut self, pitch: &Pitch) -> Result<usize> {
1158        let quotes = octave_marks(pitch)?;
1159        Ok(self.tree.add(Kind::Pitch {
1160            name: base_name(pitch),
1161            quotes,
1162        }))
1163    }
1164
1165    /// `lyMultipliedDurationFromDuration`.
1166    fn duration_node(&mut self, duration: &Duration) -> Result<usize> {
1167        let (number, dots) = duration_number(duration)?;
1168        Ok(self.tree.add(Kind::Duration { number, dots }))
1169    }
1170
1171    /// `closeMeasure`: the barline closing the measure last written.
1172    fn close_measure(&mut self) -> Option<usize> {
1173        let measure = self.current_measure.take()?;
1174        let mut bar = match measure.right_barline() {
1175            None => format!("\\bar {}", quote("|")),
1176            Some(barline) => format!("\\bar {}", quote(barline_sign(barline.bar_type()))),
1177        };
1178        bar += &format!(" %{{ end measure {} %}} ", measure.number());
1179        Some(self.embedded(bar))
1180    }
1181
1182    /// `getSchemeForPadding`: a pickup, as the 32nds of its bar it fills.
1183    fn padding(&mut self, measure: &Stream) -> Result<Option<usize>> {
1184        let padding = measure.padding_left();
1185        if padding == 0.0 {
1186            return Ok(None);
1187        }
1188        let bar =
1189            meter_of(measure, self.meter.clone()).map_or(4.0, |meter| meter.bar_quarter_length());
1190        let remaining = bar - padding;
1191        if remaining <= 0.0 {
1192            return Err(lily_error("your first pickup measure is non-existent!"));
1193        }
1194        Ok(Some(self.embedded(format!(
1195            "\\partial 32*{} ",
1196            (remaining * 8.0) as i64
1197        ))))
1198    }
1199}
1200
1201/// The time signature a measure says, or the one in force before it.
1202fn meter_of(measure: &Stream, fallback: Option<TimeSignature>) -> Option<TimeSignature> {
1203    measure
1204        .events()
1205        .iter()
1206        .find_map(|event| match event.element() {
1207            StreamElement::TimeSignature(meter) => Some(meter.clone()),
1208            _ => None,
1209        })
1210        .or(fallback)
1211}
1212
1213/// A duration, or a quarter where there is none.
1214fn element_duration(duration: Option<&Duration>) -> Duration {
1215    duration.cloned().unwrap_or_else(Duration::quarter)
1216}
1217
1218/// The note-value number LilyPond writes for a duration and its dots.
1219fn duration_number(duration: &Duration) -> Result<(String, u32)> {
1220    let name = crate::braille::basic::duration_type(duration);
1221    let number: FloatType = match name.as_str() {
1222        "duplex-maxima" => 0.0625,
1223        "maxima" => 0.125,
1224        "longa" => 0.25,
1225        "breve" => 0.5,
1226        "zero" => 0.0,
1227        "inexpressible" | "complex" => {
1228            return Err(lily_error(format!(
1229                "DurationException for durationObject {}: Could not determine durationNumber from {name}",
1230                duration_repr(duration)
1231            )));
1232        }
1233        "whole" => 1.0,
1234        "half" => 2.0,
1235        "quarter" => 4.0,
1236        "eighth" => 8.0,
1237        other => other
1238            .trim_end_matches(|c: char| c.is_ascii_alphabetic())
1239            .parse()
1240            .unwrap_or(0.0),
1241    };
1242    if number == 0.0 {
1243        return Err(lily_error(format!(
1244            "Cannot translate an object of zero duration {}",
1245            duration_repr(duration)
1246        )));
1247    }
1248    let written = if number < 1.0 {
1249        if number == 0.5 {
1250            "\\breve".to_string()
1251        } else if number == 0.25 {
1252            "\\longa".to_string()
1253        } else {
1254            return Err(lily_error("Cannot support durations longer than longa"));
1255        }
1256    } else {
1257        format!("{}", number as i64)
1258    };
1259    Ok((written, crate::braille::basic::duration_dots(duration)))
1260}
1261
1262fn duration_repr(duration: &Duration) -> String {
1263    format!(
1264        "<music21.duration.Duration {}>",
1265        crate::braille::basic::quarter_length_repr(duration.quarter_length())
1266    )
1267}
1268
1269/// `baseNameFromPitch`: the letter, with LilyPond's Dutch accidental.
1270fn base_name(pitch: &Pitch) -> String {
1271    let mut name = pitch.step().as_char().to_ascii_lowercase().to_string();
1272    if let Some(accidental) = pitch.written_accidental() {
1273        name += match accidental.name() {
1274            "double-sharp" => "isis",
1275            "double-flat" => "eses",
1276            "one-and-a-half-sharp" => "isih",
1277            "one-and-a-half-flat" => "eseh",
1278            "sharp" => "is",
1279            "flat" => "es",
1280            "half-sharp" => "ih",
1281            "half-flat" => "eh",
1282            _ => "",
1283        };
1284    }
1285    name
1286}
1287
1288/// `octaveCharactersFromPitch`: commas below the third octave, quotes above.
1289fn octave_marks(pitch: &Pitch) -> Result<String> {
1290    let Some(octave) = pitch.octave() else {
1291        return Err(lily_error(
1292            "a pitch with no octave has no place on the staff",
1293        ));
1294    };
1295    Ok(if octave < 3 {
1296        ",".repeat((3 - octave) as usize)
1297    } else {
1298        "'".repeat((octave - 3) as usize)
1299    })
1300}
1301
1302/// An accidental always shown is forced, one in parentheses cautionary.
1303fn accidental_marks(pitch: &Pitch) -> Vec<Item> {
1304    let mut marks = Vec::new();
1305    if let Some(accidental) = pitch.written_accidental() {
1306        if accidental.display_type() == "always" {
1307            marks.push(Item::Text("! ".to_string()));
1308        }
1309        if accidental.display_style() == "parentheses" {
1310            marks.push(Item::Text("? ".to_string()));
1311        }
1312    }
1313    marks
1314}
1315
1316/// `postEventsFromObject`: a tie going on, and fermatas.
1317fn post_events(tie: Option<TieType>, expressions: &[Expression]) -> Vec<String> {
1318    let mut post = Vec::new();
1319    if tie.is_some_and(|tie| tie != TieType::Stop) {
1320        post.push("~ ".to_string());
1321    }
1322    for expression in expressions {
1323        if matches!(expression, Expression::Fermata(_)) {
1324            post.push("\\fermata ".to_string());
1325        }
1326    }
1327    post
1328}
1329
1330/// `lyEmbeddedScmFromClef`.
1331fn clef_scheme(clef: &Clef) -> String {
1332    let kind = clef.kind();
1333    let classes: Vec<&str> = std::iter::once(kind.class_name())
1334        .chain(kind.parents().iter().copied())
1335        .collect();
1336    let name = [
1337        ("Treble8vbClef", "treble_8"),
1338        ("Treble8vaClef", "treble^8"),
1339        ("TrebleClef", "treble"),
1340        ("Bass8vbClef", "bass_8"),
1341        ("Bass8vaClef", "bass^8"),
1342        ("BassClef", "bass"),
1343        ("AltoClef", "alto"),
1344        ("TenorClef", "tenor"),
1345        ("SopranoClef", "soprano"),
1346        ("PercussionClef", "percussion"),
1347    ]
1348    .iter()
1349    .find(|(class, _)| classes.contains(class))
1350    .map_or("", |(_, name)| name);
1351    format!("\\clef {}\n", quote(name))
1352}
1353
1354/// `lyEmbeddedScmFromKeySignature`.
1355///
1356/// The mode is written as a LilyPond command, so only a name from
1357/// [`crate::key::MODES`] is written. A key holding any other mode, which
1358/// only a deserialized one can, is written as the major key of its
1359/// signature.
1360fn key_scheme(key: &Key) -> String {
1361    if !crate::key::MODES.contains(&key.mode()) {
1362        return key_scheme(&key.key_signature().as_key("major"));
1363    }
1364    format!("\\key {} \\{} \n", base_name(&key.tonic()), key.mode())
1365}
1366
1367/// `lyEmbeddedScmFromTimeSignature`.
1368fn time_scheme(meter: &TimeSignature) -> String {
1369    format!("\\time {}\n", meter.ratio_string())
1370}
1371
1372/// `lyEmbeddedScmFromMetronomeMark`: nothing for a mark with no number.
1373fn tempo_scheme(mark: &MetronomeMark) -> Result<Option<String>> {
1374    let Some(number) = mark.number() else {
1375        return Ok(None);
1376    };
1377    let (steno, dots) = duration_number(mark.referent())?;
1378    let steno = format!("{steno}{} ", ".".repeat(dots as usize));
1379    Ok(Some(format!(
1380        "\\tempo {steno} = {}\n",
1381        if number.fract() == 0.0 {
1382            format!("{}", number as i64)
1383        } else {
1384            crate::statistics::python_repr(number)
1385        }
1386    )))
1387}
1388
1389/// `barlineDict`: LilyPond's sign for a barline.
1390fn barline_sign(bar: BarlineType) -> &'static str {
1391    match bar {
1392        BarlineType::Regular => "|",
1393        BarlineType::Dotted => ";",
1394        BarlineType::Dashed => "!",
1395        BarlineType::Heavy => ".",
1396        BarlineType::Double => "||",
1397        BarlineType::Final => "|.",
1398        BarlineType::HeavyLight => ".|",
1399        BarlineType::HeavyHeavy => "..",
1400        BarlineType::Tick => "'",
1401        BarlineType::Short => ",",
1402        BarlineType::None => "",
1403    }
1404}
1405
1406/// `staffLines`: the first staff layout at the start that says, else five.
1407fn staff_lines(stream: &Stream) -> i64 {
1408    for (offset, element) in stream.recurse() {
1409        if offset > 0.0 {
1410            break;
1411        }
1412        if let StreamElement::Layout(crate::layout::Layout::Staff(staff)) = element
1413            && let Some(lines) = staff.staff_lines
1414        {
1415            return lines;
1416        }
1417    }
1418    5
1419}
1420
1421/// `Stream.lyrics(skipTies=True)`: for each verse, the lyric of every note
1422/// the stream holds, or of its measures, a note tied over skipped.
1423fn stream_lyrics(stream: &Stream) -> Vec<(i64, Vec<Option<&crate::notation::Lyric>>)> {
1424    let mut verses: Vec<(i64, Vec<Option<&crate::notation::Lyric>>)> = Vec::new();
1425    let mut count = 0usize;
1426    let mut notes: Vec<&StreamElement> = Vec::new();
1427    for event in stream.events() {
1428        match event.element() {
1429            StreamElement::Stream(measure) if measure.kind() == StreamKind::Measure => {
1430                notes.extend(
1431                    measure
1432                        .events()
1433                        .iter()
1434                        .map(|event| event.element())
1435                        .filter(|element| is_not_rest(element)),
1436                );
1437            }
1438            element if is_not_rest(element) => notes.push(element),
1439            _ => {}
1440        }
1441    }
1442    for element in notes {
1443        let tie = match element {
1444            StreamElement::Note(note) => note.tie().map(|tie| tie.tie_type()),
1445            StreamElement::Chord(chord) => chord.tie().map(|tie| tie.tie_type()),
1446            _ => None,
1447        };
1448        if tie.is_some_and(|tie| tie != TieType::Start) {
1449            continue;
1450        }
1451        let lyrics: &[crate::notation::Lyric] = match element {
1452            StreamElement::Note(note) => note.lyrics(),
1453            StreamElement::Chord(chord) => chord.lyrics(),
1454            _ => &[],
1455        };
1456        let mut said: Vec<i64> = Vec::new();
1457        for lyric in lyrics {
1458            let number = lyric.number() as i64;
1459            match verses.iter_mut().find(|(known, _)| *known == number) {
1460                Some((_, list)) => list.push(Some(lyric)),
1461                None => {
1462                    let mut list = vec![None; count];
1463                    list.push(Some(lyric));
1464                    verses.push((number, list));
1465                }
1466            }
1467            said.push(number);
1468        }
1469        for (number, list) in &mut verses {
1470            if !said.contains(number) {
1471                list.push(None);
1472            }
1473        }
1474        count += 1;
1475    }
1476    verses.sort_by_key(|(number, _)| *number);
1477    verses
1478}
1479
1480/// music21's `NotRest`: a note, chord, stroke or chord symbol.
1481fn is_not_rest(element: &StreamElement) -> bool {
1482    matches!(
1483        element,
1484        StreamElement::Note(_)
1485            | StreamElement::Chord(_)
1486            | StreamElement::Unpitched(_)
1487            | StreamElement::PercussionChord(_)
1488            | StreamElement::ChordSymbol(_)
1489    )
1490}
1491
1492/// A stream written as LilyPond, as music21's `LilypondConverter`'s
1493/// `textFromMusic21Object` writes it: a score's parts each on a staff, its
1494/// voices, measures with their barlines, pickups, clefs, keys, meters,
1495/// tempi, breaks, notes, rests and chords with their beams, stems, ties,
1496/// fermatas, tuplets and colours, and each part's lyrics. A stream that is
1497/// not a score is written as the one part of a score.
1498///
1499/// A stream with no id is named by a number of the writer's own, where
1500/// music21 takes the address of the object, which changes from run to run.
1501///
1502/// # Errors
1503///
1504/// A note of no length, or of one longer than a longa or no note value
1505/// writes, or a pickup longer than its bar, which music21 refuses.
1506pub fn to_lilypond(stream: &Stream, options: &LilyOptions) -> Result<String> {
1507    let wrapped;
1508    let score = if stream.kind() == StreamKind::Score {
1509        stream
1510    } else {
1511        let mut score = Stream::with_kind(StreamKind::Score);
1512        if matches!(stream.kind(), StreamKind::Measure | StreamKind::Voice) {
1513            let mut part = Stream::with_kind(StreamKind::Part);
1514            part.insert(0.0, StreamElement::Stream(Box::new(stream.clone())));
1515            score.insert(0.0, StreamElement::Stream(Box::new(part)));
1516        } else {
1517            score.insert(0.0, StreamElement::Stream(Box::new(stream.clone())));
1518        }
1519        wrapped = score;
1520        &wrapped
1521    };
1522    let mut converter = Converter::new();
1523    converter.load_score(score, options)?;
1524    let written = converter.tree.str_of(converter.top);
1525    Ok(collapse_blank_lines(&written).trim().to_string())
1526}
1527
1528/// `re.sub(r'\s*\n\s*\n', '\n', text)`: a run of whitespace holding two
1529/// newlines or more, up to its last newline, is one newline.
1530fn collapse_blank_lines(text: &str) -> String {
1531    let chars: Vec<char> = text.chars().collect();
1532    let mut out = String::with_capacity(text.len());
1533    let mut index = 0;
1534    while index < chars.len() {
1535        if !chars[index].is_whitespace() {
1536            out.push(chars[index]);
1537            index += 1;
1538            continue;
1539        }
1540        let mut end = index;
1541        while end < chars.len() && chars[end].is_whitespace() {
1542            end += 1;
1543        }
1544        let newlines: Vec<usize> = (index..end).filter(|&at| chars[at] == '\n').collect();
1545        if let [_, .., last] = newlines[..] {
1546            out.push('\n');
1547            out.extend(&chars[last + 1..end]);
1548        } else {
1549            out.extend(&chars[index..end]);
1550        }
1551        index = end;
1552    }
1553    out
1554}
1555
1556#[cfg(test)]
1557mod tests {
1558    use super::*;
1559    use crate::tinynotation::from_tiny_notation;
1560
1561    #[test]
1562    fn blank_lines_are_closed_up_as_python_closes_them() {
1563        assert_eq!(collapse_blank_lines("a \n \n b"), "a\n b");
1564        assert_eq!(collapse_blank_lines("a\n\n\nb"), "a\nb");
1565        assert_eq!(collapse_blank_lines("a\nb"), "a\nb");
1566    }
1567
1568    #[test]
1569    fn ids_are_made_of_letters_as_music21_makes_them() {
1570        assert_eq!(letters_only("P1"), "Px");
1571        assert_eq!(letters_only("Soprano"), "Soprano");
1572    }
1573
1574    #[test]
1575    fn a_line_is_written_as_music21_writes_it() {
1576        let line = from_tiny_notation("4/4 c4 d8 e f#4. g8 a2 r2").unwrap();
1577        let text = to_lilypond(&line, &LilyOptions::default()).unwrap();
1578        assert!(text.contains("\\time 4/4"));
1579        assert!(text.contains("fis' 4."));
1580        assert!(text.contains("\\bar \"|.\"  %{ end measure 2 %}"));
1581    }
1582
1583    /// Text from a score that would end its string early and run Scheme.
1584    const SCHEME_CALL: &str = "\\\" #(system \"touch x\") %";
1585    /// That text as one LilyPond string.
1586    const ESCAPED: &str = "\"\\\\\\\" #(system \\\"touch x\\\") %\"";
1587
1588    fn one_note(edit: impl FnOnce(&mut crate::Note)) -> Stream {
1589        let mut note = crate::Note::from_name("C4").unwrap();
1590        edit(&mut note);
1591        let mut line = Stream::new();
1592        line.push(note);
1593        line
1594    }
1595
1596    #[test]
1597    fn titles_and_lyrics_stay_inside_their_strings() {
1598        let line = one_note(|note| {
1599            note.lyrics_mut()
1600                .push(crate::notation::Lyric::new(SCHEME_CALL));
1601        });
1602        let mut score = Stream::with_kind(StreamKind::Score);
1603        score.push(line);
1604        let mut metadata = crate::metadata::Metadata::new();
1605        metadata.add_text("title", SCHEME_CALL);
1606        score.set_metadata(Some(metadata));
1607        let text = to_lilypond(&score, &LilyOptions::default()).unwrap();
1608        assert!(text.contains(&format!("title = {ESCAPED}")), "{text}");
1609        assert_eq!(text.matches(ESCAPED).count(), 2, "{text}");
1610    }
1611
1612    #[test]
1613    fn a_colour_stays_inside_its_string() {
1614        let line = one_note(|note| note.set_color(Some(SCHEME_CALL.to_string())));
1615        let text = to_lilypond(&line, &LilyOptions::default()).unwrap();
1616        assert!(
1617            text.contains(&format!("\\override NoteHead.color = {ESCAPED}\n")),
1618            "{text}"
1619        );
1620        assert!(
1621            text.contains(&format!("\\override Stem.color = {ESCAPED}\n")),
1622            "{text}"
1623        );
1624    }
1625
1626    #[test]
1627    fn a_key_writes_only_a_mode_lilypond_knows() {
1628        let mut line = Stream::new();
1629        line.push(Key::new(
1630            crate::Pitch::from_name("C").unwrap(),
1631            "major #(system \"touch x\") %",
1632            0,
1633        ));
1634        line.push(crate::Note::from_name("C4").unwrap());
1635        let text = to_lilypond(&line, &LilyOptions::default()).unwrap();
1636        assert!(!text.contains("#(system"), "{text}");
1637        assert!(text.contains("\\key c \\major"), "{text}");
1638
1639        let mut line = Stream::new();
1640        line.push(Key::from_tonic_mode("D", "dorian").unwrap());
1641        line.push(crate::Note::from_name("D4").unwrap());
1642        let text = to_lilypond(&line, &LilyOptions::default()).unwrap();
1643        assert!(text.contains("\\key d \\dorian"), "{text}");
1644    }
1645}