Skip to main content

music21_rs/note/
mod.rs

1pub(crate) mod generalnote;
2pub(crate) mod notrest;
3
4use crate::defaults::{FloatType, IntegerType};
5use crate::duration::Duration;
6use crate::error::Result;
7use crate::pitch::Pitch;
8
9use generalnote::GeneralNoteTrait;
10use notrest::NotRest;
11use std::fmt::{Display, Formatter};
12use std::str::FromStr;
13
14#[derive(Clone, Debug)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16/// A pitched note.
17pub struct Note {
18    notrest: NotRest,
19    pub(crate) _pitch: Pitch,
20}
21
22impl Note {
23    /// Builds a note from a pitch name such as `"C#4"` or `"E-"`.
24    pub fn from_name(name: impl Into<String>) -> Result<Self> {
25        Self::new(Option::<Pitch>::None, None, None, Some(name.into()))
26    }
27
28    /// Builds a note from an existing [`Pitch`].
29    pub fn from_pitch(pitch: Pitch) -> Result<Self> {
30        Self::new(Some(pitch), None, None, None)
31    }
32
33    /// Returns the note's pitch.
34    pub fn pitch(&self) -> &Pitch {
35        &self._pitch
36    }
37
38    /// Returns the pitch name without an octave, such as `"C#"` or `"E-"`.
39    pub fn pitch_name(&self) -> String {
40        self._pitch.name()
41    }
42
43    /// Returns the pitch name with an octave when one is set.
44    pub fn pitch_name_with_octave(&self) -> String {
45        self._pitch.name_with_octave()
46    }
47
48    /// Returns the note duration when one has been assigned.
49    pub fn duration(&self) -> Option<&Duration> {
50        self.notrest.duration().as_ref()
51    }
52
53    /// Assigns a duration to the note.
54    pub fn set_duration(&mut self, duration: Duration) {
55        self.notrest.set_duration(&duration);
56    }
57
58    /// Returns a copy of this note with the supplied duration.
59    pub fn with_duration(mut self, duration: Duration) -> Self {
60        self.set_duration(duration);
61        self
62    }
63
64    pub(crate) fn new<T>(
65        pitch: Option<T>,
66        duration: Option<Duration>,
67        name: Option<String>,
68        name_with_octave: Option<String>,
69    ) -> Result<Self>
70    where
71        T: IntoPitch,
72    {
73        let _pitch = match pitch {
74            Some(pitch) => pitch.into_pitch(),
75            None => Ok({
76                let name = match name_with_octave {
77                    Some(name_with_octave) => name_with_octave,
78                    None => match name {
79                        Some(name) => name,
80                        None => "C4".to_string(),
81                    },
82                };
83
84                Pitch::from_name(name)?
85            }),
86        }?;
87
88        Ok(Self {
89            notrest: NotRest::new(duration),
90            _pitch,
91        })
92    }
93}
94
95impl GeneralNoteTrait for Note {
96    fn duration(&self) -> &Option<Duration> {
97        self.notrest.duration()
98    }
99
100    fn set_duration(&mut self, duration: &Duration) {
101        self.notrest.set_duration(duration);
102    }
103}
104
105impl FromStr for Note {
106    type Err = crate::error::Error;
107
108    fn from_str(value: &str) -> Result<Self> {
109        Self::from_name(value)
110    }
111}
112
113impl TryFrom<&str> for Note {
114    type Error = crate::error::Error;
115
116    fn try_from(value: &str) -> Result<Self> {
117        Self::from_name(value)
118    }
119}
120
121impl TryFrom<String> for Note {
122    type Error = crate::error::Error;
123
124    fn try_from(value: String) -> Result<Self> {
125        Self::from_name(value)
126    }
127}
128
129impl TryFrom<Pitch> for Note {
130    type Error = crate::error::Error;
131
132    fn try_from(value: Pitch) -> Result<Self> {
133        Self::from_pitch(value)
134    }
135}
136
137impl TryFrom<&Pitch> for Note {
138    type Error = crate::error::Error;
139
140    fn try_from(value: &Pitch) -> Result<Self> {
141        Self::from_pitch(value.clone())
142    }
143}
144
145impl TryFrom<IntegerType> for Note {
146    type Error = crate::error::Error;
147
148    fn try_from(value: IntegerType) -> Result<Self> {
149        Note::new(Some(value), None, None, None)
150    }
151}
152
153impl Display for Note {
154    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
155        write!(f, "{}", self.pitch_name_with_octave())
156    }
157}
158
159/// Converts a single note-like value into a [`Note`].
160///
161/// This is useful when constructing vectors or other collections that are
162/// later passed to APIs such as `Chord::new`.
163pub trait IntoNote {
164    /// Whether this value came from an integer pitch class or MIDI-like number.
165    const FROM_INTEGER_PITCH: bool = false;
166
167    /// Converts the value into a note.
168    fn try_into_note(self) -> Result<Note>;
169}
170
171impl IntoNote for Note {
172    fn try_into_note(self) -> Result<Note> {
173        Ok(self)
174    }
175}
176
177impl IntoNote for &Note {
178    fn try_into_note(self) -> Result<Note> {
179        Ok(self.clone())
180    }
181}
182
183impl IntoNote for Pitch {
184    fn try_into_note(self) -> Result<Note> {
185        Note::new(Some(self), None, None, None)
186    }
187}
188
189impl IntoNote for &Pitch {
190    fn try_into_note(self) -> Result<Note> {
191        Note::new(Some(self.clone()), None, None, None)
192    }
193}
194
195impl IntoNote for String {
196    fn try_into_note(self) -> Result<Note> {
197        Note::new(Some(self), None, None, None)
198    }
199}
200
201impl IntoNote for &String {
202    fn try_into_note(self) -> Result<Note> {
203        Note::new(Some(self.to_string()), None, None, None)
204    }
205}
206
207impl IntoNote for &str {
208    fn try_into_note(self) -> Result<Note> {
209        Note::new(Some(self), None, None, None)
210    }
211}
212
213impl IntoNote for IntegerType {
214    const FROM_INTEGER_PITCH: bool = true;
215
216    fn try_into_note(self) -> Result<Note> {
217        Note::new(Some(self), None, None, None)
218    }
219}
220
221pub(crate) trait IntoPitch {
222    fn into_pitch(self) -> Result<Pitch>;
223}
224
225impl IntoPitch for Pitch {
226    fn into_pitch(self) -> Result<Pitch> {
227        Ok(self.clone())
228    }
229}
230
231impl IntoPitch for String {
232    fn into_pitch(self) -> Result<Pitch> {
233        Pitch::from_name(self)
234    }
235}
236
237impl IntoPitch for &str {
238    fn into_pitch(self) -> Result<Pitch> {
239        Pitch::from_name(self)
240    }
241}
242
243impl IntoPitch for IntegerType {
244    fn into_pitch(self) -> Result<Pitch> {
245        Pitch::from_number(self as FloatType)
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::{IntoNote, Note};
252    use crate::defaults::IntegerType;
253    use crate::pitch::Pitch;
254
255    #[test]
256    fn into_note_accepts_note_like_inputs() {
257        fn from_integer_pitch<T: IntoNote>() -> bool {
258            T::FROM_INTEGER_PITCH
259        }
260
261        assert!(!from_integer_pitch::<&str>());
262        assert!(from_integer_pitch::<IntegerType>());
263
264        let note = Note::from_name("C4").unwrap();
265        assert_eq!(
266            note.clone()
267                .try_into_note()
268                .unwrap()
269                .pitch_name_with_octave(),
270            "C4"
271        );
272
273        let borrowed_note = Note::from_name("D4").unwrap();
274        assert_eq!(
275            (&borrowed_note)
276                .try_into_note()
277                .unwrap()
278                .pitch_name_with_octave(),
279            "D4"
280        );
281
282        let pitch = Pitch::from_name("E4").unwrap();
283        assert_eq!(
284            pitch.try_into_note().unwrap().pitch_name_with_octave(),
285            "E4"
286        );
287
288        let borrowed_pitch = Pitch::from_name("F4").unwrap();
289        assert_eq!(
290            (&borrowed_pitch)
291                .try_into_note()
292                .unwrap()
293                .pitch_name_with_octave(),
294            "F4"
295        );
296
297        assert_eq!(
298            "G4".to_string()
299                .try_into_note()
300                .unwrap()
301                .pitch_name_with_octave(),
302            "G4"
303        );
304
305        let owned_name = "A4".to_string();
306        assert_eq!(
307            (&owned_name)
308                .try_into_note()
309                .unwrap()
310                .pitch_name_with_octave(),
311            "A4"
312        );
313
314        assert_eq!("B4".try_into_note().unwrap().pitch_name_with_octave(), "B4");
315
316        assert_eq!(
317            (60 as IntegerType)
318                .try_into_note()
319                .unwrap()
320                .pitch_name_with_octave(),
321            "C4"
322        );
323    }
324
325    #[test]
326    fn note_supports_rust_conversion_traits() {
327        let parsed: Note = "C#4".parse().unwrap();
328        assert_eq!(parsed.to_string(), "C#4");
329
330        let from_pitch = Note::try_from(Pitch::from_name("D4").unwrap()).unwrap();
331        assert_eq!(from_pitch.pitch_name_with_octave(), "D4");
332
333        let from_integer = Note::try_from(60 as IntegerType).unwrap();
334        assert_eq!(from_integer.pitch_name_with_octave(), "C4");
335    }
336}