music21/analysis/
metrical.rs1use crate::{
4 defaults::FloatType,
5 error::{Error, Result},
6 meter::TimeSignature,
7 notation::TieType,
8 stream::{Stream, StreamElement, StreamKind},
9};
10
11pub 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
58pub 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
125fn 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
144fn 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
169pub 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}