Skip to main content

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