1use crate::chord::Chord;
10use crate::defaults::IntegerType;
11use crate::duration::Duration;
12use crate::error::Result;
13use crate::instrument::Instrument;
14use crate::note::Note;
15use crate::pitch::Pitch;
16
17#[derive(Clone, Debug)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[must_use]
32pub struct Unpitched {
33 written: Note,
36}
37
38impl Default for Unpitched {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44impl PartialEq for Unpitched {
45 fn eq(&self, other: &Self) -> bool {
48 self.display_name() == other.display_name()
49 && self.stored_instrument() == other.stored_instrument()
50 && self.duration() == other.duration()
51 }
52}
53
54impl Unpitched {
55 pub fn new() -> Self {
58 Self::from_display_name("B4").expect("B4 is a pitch name")
59 }
60
61 pub fn from_display_name(name: &str) -> Result<Self> {
65 let named = Pitch::from_name(name)?;
66 Self::displayed_at(named.step().as_char(), named.implicit_octave())
67 }
68
69 pub fn displayed_at(step: char, octave: IntegerType) -> Result<Self> {
71 Ok(Self {
72 written: Note::from_name(format!("{step}{octave}"))?,
73 })
74 }
75
76 pub fn display_step(&self) -> char {
79 self.written.pitch().step().as_char()
80 }
81
82 pub fn display_octave(&self) -> IntegerType {
84 self.written.pitch().implicit_octave()
85 }
86
87 pub fn display_pitch(&self) -> &Pitch {
90 self.written.pitch()
91 }
92
93 pub fn display_name(&self) -> String {
95 self.written.pitch().name_with_octave()
96 }
97
98 pub fn set_display(&mut self, step: char, octave: IntegerType) -> Result<()> {
100 let pitch = Pitch::from_name(format!("{step}{octave}"))?;
101 self.written.set_pitch(pitch);
102 Ok(())
103 }
104
105 pub fn duration(&self) -> Option<&Duration> {
107 self.written.duration()
108 }
109
110 pub fn with_duration(mut self, duration: Duration) -> Self {
112 self.written.set_duration(duration);
113 self
114 }
115
116 pub fn stored_instrument(&self) -> Option<&Instrument> {
119 self.written.stored_instrument()
120 }
121
122 pub fn written(&self) -> &Note {
126 &self.written
127 }
128
129 pub fn written_mut(&mut self) -> &mut Note {
132 &mut self.written
133 }
134}
135
136#[derive(Clone, Debug)]
138#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
139pub enum PercussionNote {
140 Unpitched(Unpitched),
142 Note(Note),
144}
145
146impl From<Unpitched> for PercussionNote {
147 fn from(value: Unpitched) -> Self {
148 Self::Unpitched(value)
149 }
150}
151
152impl From<Note> for PercussionNote {
153 fn from(value: Note) -> Self {
154 Self::Note(value)
155 }
156}
157
158#[derive(Clone, Debug)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179#[must_use]
180pub struct PercussionChord {
181 written: Chord,
184 unpitched: Vec<bool>,
186}
187
188impl PercussionChord {
189 pub fn new(members: Vec<PercussionNote>) -> Result<Self> {
191 let unpitched = members
192 .iter()
193 .map(|member| matches!(member, PercussionNote::Unpitched(_)))
194 .collect();
195 let notes: Vec<Note> = members
196 .into_iter()
197 .map(|member| match member {
198 PercussionNote::Unpitched(stroke) => stroke.written,
199 PercussionNote::Note(note) => note,
200 })
201 .collect();
202 Ok(Self {
203 written: Chord::new(notes)?,
204 unpitched,
205 })
206 }
207
208 pub fn len(&self) -> usize {
210 self.unpitched.len()
211 }
212
213 pub fn is_empty(&self) -> bool {
215 self.unpitched.is_empty()
216 }
217
218 pub fn is_unpitched(&self, index: usize) -> bool {
220 self.unpitched.get(index).copied().unwrap_or(false)
221 }
222
223 pub fn members(&self) -> Vec<PercussionNote> {
225 self.written
226 .notes()
227 .iter()
228 .zip(&self.unpitched)
229 .map(|(note, unpitched)| {
230 if *unpitched {
231 PercussionNote::Unpitched(Unpitched {
232 written: note.clone(),
233 })
234 } else {
235 PercussionNote::Note(note.clone())
236 }
237 })
238 .collect()
239 }
240
241 pub fn pitches(&self) -> Vec<Pitch> {
243 self.written
244 .notes()
245 .iter()
246 .zip(&self.unpitched)
247 .filter(|(_, unpitched)| !**unpitched)
248 .map(|(note, _)| note.pitch().clone())
249 .collect()
250 }
251
252 pub fn duration(&self) -> Option<&Duration> {
254 self.written.duration()
255 }
256
257 pub fn with_duration(mut self, duration: Duration) -> Self {
259 self.written.set_duration(duration);
260 self
261 }
262
263 pub fn written(&self) -> &Chord {
268 &self.written
269 }
270
271 pub fn written_mut(&mut self) -> &mut Chord {
274 &mut self.written
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281
282 #[test]
283 fn a_stroke_is_displayed_without_an_accidental() {
284 let stroke = Unpitched::from_display_name("D#5").unwrap();
286 assert_eq!(stroke.display_name(), "D5");
287 assert_eq!(Unpitched::new().display_name(), "B4");
288 }
289
290 #[test]
291 fn strokes_compare_by_where_they_are_displayed() {
292 let one = Unpitched::from_display_name("B4").unwrap();
293 assert_eq!(one, Unpitched::new());
294 assert_ne!(one, Unpitched::from_display_name("A4").unwrap());
295 }
296
297 #[test]
298 fn a_percussion_chord_keeps_its_order() {
299 let chord = PercussionChord::new(vec![
300 Unpitched::from_display_name("B3").unwrap().into(),
301 Unpitched::from_display_name("E3").unwrap().into(),
302 ])
303 .unwrap();
304 let names: Vec<String> = chord
305 .written()
306 .notes()
307 .iter()
308 .map(|note| note.pitch().name_with_octave())
309 .collect();
310 assert_eq!(names, ["B3", "E3"]);
311 assert!(chord.pitches().is_empty());
312 }
313}