Skip to main content

music21_rs/analysis/
metrical.rs

1//! Metrical and melodic accent: music21's `analysis.metrical`.
2
3use crate::{
4    defaults::FloatType,
5    error::{Error, Result},
6    meter::TimeSignature,
7    notation::TieType,
8    stream::{Stream, StreamElement, StreamKind},
9};
10
11/// How many levels of the metrical hierarchy start at each note and rest of
12/// a part's measures, as positions in [`Stream::leaves`] beside the count:
13/// the depths music21's `labelBeatDepth` marks.
14///
15/// Each measure is read in the meter it states, else the one in force where
16/// it starts, else four-four, nested three levels deep, and every note,
17/// chord and rest it holds itself -- not those in its voices -- is counted
18/// where it starts. A note ending a tie is passed over.
19///
20/// # Errors
21///
22/// A note standing outside its measure's bar.
23pub fn beat_depths(part: &Stream) -> Result<Vec<(usize, u8)>> {
24    let mut depths = Vec::new();
25    let mut position = 0;
26    for event in part.events() {
27        let StreamElement::Stream(measure) = event.element() else {
28            position += 1;
29            continue;
30        };
31        let leaves = measure.leaves().len();
32        if measure.kind() != StreamKind::Measure {
33            position += leaves;
34            continue;
35        }
36        let mut meter = measure_meter(part, measure, event.offset())?;
37        meter
38            .beat_sequence_mut()
39            .subdivide_nested_hierarchy(3, None, true)?;
40        let mut inside = position;
41        for own in measure.events() {
42            match own.element() {
43                StreamElement::Stream(nested) => inside += nested.leaves().len(),
44                element => {
45                    if is_note_or_rest(element) && !ends_tie(element) {
46                        depths.push((inside, meter.beat_depth(own.offset())?));
47                    }
48                    inside += 1;
49                }
50            }
51        }
52        position += leaves;
53    }
54    Ok(depths)
55}
56
57/// Marks each note and rest of a part's measures with a lyric of a star for
58/// every level of the metrical hierarchy starting where it does: music21's
59/// `labelBeatDepth`. The depths are [`beat_depths`]'.
60///
61/// ```
62/// use music21_rs::analysis::metrical::label_beat_depth;
63/// use music21_rs::stream::StreamElement;
64/// use music21_rs::tinynotation::from_tiny_notation;
65///
66/// let mut part = from_tiny_notation("4/4 c8 c c c c c c c")?;
67/// label_beat_depth(&mut part)?;
68/// let stars: Vec<String> = part
69///     .leaves()
70///     .into_iter()
71///     .filter_map(|(_, element)| match element {
72///         StreamElement::Note(note) => {
73///             Some(note.lyrics().iter().map(|lyric| lyric.text()).collect())
74///         }
75///         _ => None,
76///     })
77///     .collect();
78/// assert_eq!(stars, ["****", "*", "**", "*", "***", "*", "**", "*"]);
79/// # Ok::<(), music21_rs::Error>(())
80/// ```
81///
82/// # Errors
83///
84/// As [`beat_depths`], or a chord symbol to mark, which carries no lyric
85/// here.
86pub fn label_beat_depth(part: &mut Stream) -> Result<()> {
87    let depths = beat_depths(part)?;
88    let mut position = 0;
89    let mut next = depths.iter().peekable();
90    let mut refused = None;
91    part.for_each_mut(&mut |_, element| {
92        if let Some(&&(at, depth)) = next.peek()
93            && at == position
94        {
95            next.next();
96            for _ in 0..depth {
97                let added = match element {
98                    StreamElement::Note(note) => note.add_lyric("*", None, false),
99                    StreamElement::Chord(chord) => chord.add_lyric("*", None, false),
100                    StreamElement::Rest(rest) => {
101                        rest.add_lyric("*", None, false);
102                        Ok(())
103                    }
104                    StreamElement::Unpitched(stroke) => {
105                        stroke.written_mut().add_lyric("*", None, false)
106                    }
107                    StreamElement::PercussionChord(chord) => {
108                        chord.written_mut().add_lyric("*", None, false)
109                    }
110                    _ => Err(Error::Analysis(
111                        "a chord symbol carries no lyric to mark its beat depth with".to_string(),
112                    )),
113                };
114                if let Err(error) = added {
115                    refused.get_or_insert(error);
116                }
117            }
118        }
119        position += 1;
120    });
121    refused.map_or(Ok(()), Err)
122}
123
124/// The meter a measure is read in: the first it holds, else the one in
125/// force in the part where it starts, else four-four.
126fn measure_meter(part: &Stream, measure: &Stream, offset: FloatType) -> Result<TimeSignature> {
127    if let Some(meter) = measure
128        .recurse()
129        .into_iter()
130        .find_map(|(_, element)| match element {
131            StreamElement::TimeSignature(meter) => Some(meter.clone()),
132            _ => None,
133        })
134    {
135        return Ok(meter);
136    }
137    match part.time_signature_at(offset) {
138        Some(meter) => Ok(meter),
139        None => TimeSignature::new(4, 4),
140    }
141}
142
143/// music21's `notesAndRests`: anything that sounds or rests.
144fn is_note_or_rest(element: &StreamElement) -> bool {
145    matches!(
146        element,
147        StreamElement::Note(_)
148            | StreamElement::Chord(_)
149            | StreamElement::Rest(_)
150            | StreamElement::Unpitched(_)
151            | StreamElement::PercussionChord(_)
152            | StreamElement::ChordSymbol(_)
153    )
154}
155
156fn ends_tie(element: &StreamElement) -> bool {
157    let tie = match element {
158        StreamElement::Note(note) => note.tie(),
159        StreamElement::Chord(chord) => chord.tie(),
160        StreamElement::Rest(rest) => rest.tie(),
161        StreamElement::Unpitched(stroke) => stroke.written().tie(),
162        StreamElement::PercussionChord(chord) => chord.written().tie(),
163        _ => None,
164    };
165    tie.is_some_and(|tie| tie.tie_type() == TieType::Stop)
166}
167
168/// The melodic accent of each note of a line, after Thomassen (1982) as
169/// Huron and Royal (1996) apply it: music21's `thomassenMelodicAccent`.
170///
171/// The first note takes an accent of one. Each note after it is accented by
172/// the contour around it -- a peak or a valley, a turn, a run on or a note
173/// repeated -- times what the contour before it left over for it, and the
174/// last note takes what is left over. Only pitch heights are compared, so
175/// enharmonic notes are one. The line is the notes the stream holds itself,
176/// in order.
177///
178/// ```
179/// use music21_rs::Stream;
180/// use music21_rs::analysis::metrical::thomassen_melodic_accent;
181/// use music21_rs::stream::StreamElement;
182/// use music21_rs::tinynotation::from_tiny_notation;
183///
184/// let line = from_tiny_notation("7/4 c4 c c d e d d")?.flatten();
185/// let notes = Stream::from_events(
186///     line.events()
187///         .iter()
188///         .filter(|event| matches!(event.element(), StreamElement::Note(_)))
189///         .cloned(),
190/// );
191/// let accents = thomassen_melodic_accent(&notes)?;
192/// assert_eq!(accents, [1.0, 0.0, 0.0, 0.33, 0.67 * 0.83, 0.17, 0.0]);
193/// # Ok::<(), music21_rs::Error>(())
194/// ```
195///
196/// # Errors
197///
198/// Anything in the stream but a note, once there are three elements to
199/// compare.
200pub fn thomassen_melodic_accent(line: &Stream) -> Result<Vec<FloatType>> {
201    let events = line.events();
202    let height = |index: usize| -> Result<FloatType> {
203        match events[index].element() {
204            StreamElement::Note(note) => Ok(note.pitch().ps()),
205            _ => Err(Error::Analysis(
206                "melodic accent is read off notes alone".to_string(),
207            )),
208        }
209    };
210    let last = events.len().saturating_sub(1);
211    let mut accents = Vec::with_capacity(events.len());
212    let mut carried = 1.0;
213    for index in 0..events.len() {
214        if index == 0 {
215            accents.push(1.0);
216            continue;
217        }
218        if index == last {
219            accents.push(carried);
220            continue;
221        }
222        let (before, here, after) = (height(index - 1)?, height(index)?, height(index + 1)?);
223        let (this, next) = if before == here && here == after {
224            (0.0, 0.0)
225        } else if before != here && here == after {
226            (1.0, 0.0)
227        } else if before == here {
228            (0.0, 1.0)
229        } else if before < here && here > after {
230            (0.83, 0.17)
231        } else if before > here && here < after {
232            (0.71, 0.29)
233        } else if before < here && here < after {
234            (0.33, 0.67)
235        } else if before > here && here > after {
236            (0.5, 0.5)
237        } else {
238            (0.0, 0.0)
239        };
240        accents.push(this * carried);
241        carried = next;
242    }
243    Ok(accents)
244}