music21_rs/stream/
opus.rs1use super::{Stream, StreamKind};
9use crate::error::{Error, Result};
10use crate::makenotation::make_notation;
11use crate::metadata::{Metadata, MetadataQuery};
12
13impl Stream {
14 pub fn scores(&self) -> Vec<&Stream> {
16 self.streams_of_kind(StreamKind::Score)
17 }
18
19 pub fn score_numbers(&self) -> Vec<Option<String>> {
23 self.scores()
24 .into_iter()
25 .map(|score| {
26 score
27 .metadata()
28 .and_then(|metadata| metadata.string_value("number"))
29 })
30 .collect()
31 }
32
33 pub fn score_by_number(&self, query: &MetadataQuery<'_>) -> Option<&Stream> {
54 self.score_by_field(query, "number")
55 }
56
57 pub fn score_by_title(&self, query: &MetadataQuery<'_>) -> Option<&Stream> {
62 self.score_by_field(query, "title")
63 }
64
65 fn score_by_field(&self, query: &MetadataQuery<'_>, field: &str) -> Option<&Stream> {
67 self.scores().into_iter().find(|score| {
68 score
69 .metadata()
70 .is_some_and(|metadata| metadata.search(query, Some(field)).is_some())
71 })
72 }
73
74 pub fn merge_scores(&self) -> Result<Stream> {
87 let mut merged = Stream::with_kind(StreamKind::Score);
88 let mut metadata = Metadata::new();
89 for score in self.scores() {
90 let Some(part) = score.parts().first().copied() else {
91 return Err(Error::Stream(
92 "an opus score has no part to merge".to_string(),
93 ));
94 };
95 let mut part = part.clone();
97 make_notation(&mut part)?;
98 merged.insert(0.0, part);
99
100 let Some(found) = score.metadata() else {
101 continue;
102 };
103 if metadata.get("title").is_empty()
105 && let Some(title) = found.string_value("title")
106 {
107 metadata.add_text("title", title);
108 }
109 if metadata.get("composer").is_empty()
110 && let Some(composer) = found.string_value("composer")
111 {
112 metadata.add_contributor("composer", composer);
113 }
114 }
115 merged.set_metadata(Some(metadata));
116 Ok(merged)
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::defaults::FloatType;
124 use crate::metadata::MetadataQuery;
125 use crate::note::Note;
126 use crate::{Duration, TimeSignature};
127
128 fn tune(number: &str, title: &str, composer: Option<&str>) -> Stream {
129 let mut score = Stream::with_kind(StreamKind::Score);
130 let mut metadata = Metadata::new();
131 metadata.add_text("number", number);
132 metadata.add_text("title", title);
133 if let Some(composer) = composer {
134 metadata.add_contributor("composer", composer);
135 }
136 score.set_metadata(Some(metadata));
137 let mut part = Stream::with_kind(StreamKind::Part);
138 part.insert(0.0, TimeSignature::new(2, 4).expect("2/4 is a meter"));
139 for (place, name) in ["C4", "D4", "E4", "F4"].iter().enumerate() {
140 let note = Note::from_name(name)
141 .expect("a note name")
142 .with_duration(Duration::eighth());
143 part.insert(place as FloatType * 0.5, note);
144 }
145 score.push(part);
146 score
147 }
148
149 fn opus() -> Stream {
150 let mut opus = Stream::with_kind(StreamKind::Opus);
151 opus.push(tune("1", "Vrienden, kommt alle gaere", Some("Anonymous")));
152 opus.push(tune("2", "O Venus bant", None));
153 opus
154 }
155
156 #[test]
157 fn an_opus_names_the_numbers_its_scores_carry() {
158 assert_eq!(
159 opus().score_numbers(),
160 [Some("1".to_string()), Some("2".to_string())]
161 );
162 assert_eq!(Stream::with_kind(StreamKind::Opus).score_numbers(), []);
163 }
164
165 #[test]
166 fn a_score_is_found_by_its_number_and_by_its_title() {
167 let opus = opus();
168 let second = opus
169 .score_by_number(&MetadataQuery::Text("2"))
170 .expect("the second tune");
171 assert_eq!(
172 second.metadata().and_then(Metadata::title).as_deref(),
173 Some("O Venus bant")
174 );
175 let first = opus
176 .score_by_title(&MetadataQuery::Text("kommt"))
177 .expect("the first tune");
178 assert_eq!(
179 first.metadata().and_then(Metadata::title).as_deref(),
180 Some("Vrienden, kommt alle gaere")
181 );
182 assert!(opus.score_by_number(&MetadataQuery::Text("9")).is_none());
183 }
184
185 #[test]
186 fn a_matcher_stands_in_for_music21s_regular_expression() {
187 let opus = opus();
188 let kommt = |text: &str| text.starts_with("Vrienden") && text.ends_with("gaere");
189 let found = opus
190 .score_by_title(&MetadataQuery::Matcher(&kommt))
191 .expect("the first tune");
192 assert_eq!(
193 found.metadata().and_then(Metadata::title).as_deref(),
194 Some("Vrienden, kommt alle gaere")
195 );
196 }
197
198 #[test]
199 fn merging_the_scores_makes_one_score_of_parts() {
200 let merged = opus().merge_scores().expect("two parts to merge");
201 assert_eq!(merged.kind(), StreamKind::Score);
202 assert_eq!(merged.parts().len(), 2);
203 let metadata = merged.metadata().expect("the merged metadata");
204 assert_eq!(
205 metadata.title().as_deref(),
206 Some("Vrienden, kommt alle gaere")
207 );
208 assert_eq!(
209 metadata.string_value("composer").as_deref(),
210 Some("Anonymous")
211 );
212 assert_eq!(merged.parts()[0].measures().len(), 1);
214 }
215
216 #[test]
217 fn a_score_without_a_part_cannot_be_merged() {
218 let mut opus = Stream::with_kind(StreamKind::Opus);
219 opus.push(Stream::with_kind(StreamKind::Score));
220 assert!(opus.merge_scores().is_err());
221 }
222}