Skip to main content

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