redact_composer_musical/note/
mod.rs

1use crate::{Interval, PitchClass};
2use std::ops::{Add, AddAssign, Sub, SubAssign};
3
4mod note_name;
5pub use note_name::*;
6
7mod iter;
8pub use iter::*;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13#[cfg(feature = "redact-composer")]
14use redact_composer_core::{derive::Element, elements::PlayNote};
15
16/// A musical note, corresponding to a specific frequency in 12 tone equal temperament space.
17#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19#[cfg_attr(feature = "redact-composer", derive(Element))]
20pub struct Note(pub u8);
21
22impl Note {
23    /// Returns the note's [`PitchClass`].
24    /// ```
25    /// use redact_composer_musical::{Note, NoteName::C, PitchClass};
26    ///
27    /// assert_eq!(Note::from((C, 4)).pitch_class(), PitchClass(0));
28    /// ```
29    pub fn pitch_class(&self) -> PitchClass {
30        self.0.into()
31    }
32
33    /// Returns the octave number of this note:
34    /// ```
35    /// use redact_composer_musical::{Note, NoteName::{B, C}};
36    ///
37    /// assert_eq!(Note::from((C, 0)).octave(), 0);
38    /// assert_eq!(Note::from((B, 5)).octave(), 5);
39    /// ```
40    pub fn octave(&self) -> i8 {
41        (self.0 / 12) as i8 - 1
42    }
43
44    /// Returns the interval between this note and another.
45    /// ```
46    /// use redact_composer_musical::{Interval, Note, NoteName::C};
47    ///
48    /// assert_eq!(Note::from((C, 3)).interval_with(&Note::from((C, 4))), Interval::Octave);
49    /// ```
50    pub fn interval_with(&self, other_note: &Note) -> Interval {
51        let (lower, higher) = if self.0 <= other_note.0 {
52            (self.0, other_note.0)
53        } else {
54            (other_note.0, self.0)
55        };
56
57        Interval(higher - lower)
58    }
59}
60
61impl From<(NoteName, i8)> for Note {
62    /// ```
63    /// use redact_composer_musical::{Note, NoteName::C};
64    ///
65    /// assert_eq!(Note::from((C, 4)), Note(60));
66    /// ```
67    fn from(value: (NoteName, i8)) -> Self {
68        Note::from((PitchClass::from(value.0), value.1))
69    }
70}
71
72impl From<(PitchClass, i8)> for Note {
73    /// ```
74    /// use redact_composer_musical::{Note, NoteName::C, PitchClass};
75    ///
76    /// let c3 = Note::from((PitchClass(0), 4));
77    /// assert_eq!(c3, Note(60));
78    /// ```
79    fn from(value: (PitchClass, i8)) -> Self {
80        let (note, octave) = (value.0 .0, value.1 + 1);
81        Note(note + 12 * octave as u8)
82    }
83}
84
85impl Add<Interval> for Note {
86    type Output = Note;
87    /// Adds an interval to this note, resulting in another note.
88    /// ```
89    /// use redact_composer_musical::{Interval as I, Note, NoteName::{C, G}};
90    ///
91    /// let c3 = Note::from((C, 4));
92    /// assert_eq!(c3, Note(60));
93    /// assert_eq!(c3 + I::P5, Note::from((G, 4)));
94    /// ```
95    fn add(self, rhs: Interval) -> Self::Output {
96        Note(self.0 + rhs.0)
97    }
98}
99
100impl AddAssign<Interval> for Note {
101    fn add_assign(&mut self, rhs: Interval) {
102        self.0 = self.0 + rhs.0;
103    }
104}
105
106impl Sub<Interval> for Note {
107    type Output = Note;
108    /// Subtracts an interval from this note, resulting in another note.
109    /// ```
110    /// use redact_composer_musical::{Interval as I, Note, NoteName::{C, G}};
111    ///
112    /// let c3 = Note::from((C, 3));
113    /// assert_eq!(c3 - I::P4, Note::from((G, 2)));
114    /// ```
115    fn sub(self, rhs: Interval) -> Self::Output {
116        Note(self.0 - rhs.0)
117    }
118}
119
120impl SubAssign<Interval> for Note {
121    fn sub_assign(&mut self, rhs: Interval) {
122        self.0 = self.0 - rhs.0;
123    }
124}
125
126impl PartialEq<PitchClass> for Note {
127    /// ```
128    /// use redact_composer_musical::{Note, NoteName::C, PitchClass};
129    ///
130    /// assert!(Note::from((C, 3)) == PitchClass::from(C));
131    /// ```
132    fn eq(&self, other: &PitchClass) -> bool {
133        &self.pitch_class() == other
134    }
135}
136
137impl PartialEq<Note> for (NoteName, i8) {
138    /// ```
139    /// use redact_composer_musical::{Note, NoteName::*};
140    /// assert_eq!((C, 4), Note(60));
141    /// ```
142    fn eq(&self, other: &Note) -> bool {
143        Note::from(*self).eq(other)
144    }
145}
146
147impl PartialEq<(NoteName, i8)> for Note {
148    /// ```
149    /// use redact_composer_musical::{Note, NoteName::*};
150    /// assert_eq!(Note(60), (C, 4));
151    /// ```
152    fn eq(&self, other: &(NoteName, i8)) -> bool {
153        self.eq(&Note::from(*other))
154    }
155}
156
157#[cfg(feature = "redact-composer")]
158impl Note {
159    /// Creates a [`PlayNote`] element from this note, which can then be used as a [`Segment`](redact_composer_core::Segment).
160    /// ```
161    /// use redact_composer_core::{elements::PlayNote, timing::Timing, IntoSegment};
162    /// use redact_composer_musical::{Note, NoteName::C};
163    ///
164    /// let expected_play_note = PlayNote { note: 60, velocity: 100};
165    /// let play_note = Note::from((C, 4)).play(100);
166    /// assert_eq!(play_note, expected_play_note);
167    ///
168    ///let segment = play_note.into_segment(0..480);
169    /// assert_eq!(segment.element_as::<PlayNote>(), Some(&expected_play_note));
170    /// assert_eq!(segment.timing, Timing::from(0..480));
171    /// ```
172    pub fn play(&self, velocity: u8) -> PlayNote {
173        PlayNote {
174            note: self.0,
175            velocity,
176        }
177    }
178}