1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
use crate::errors::NoteError;
use crate::num::u7;
use crate::Result;
/// Represents a music note, with a pitch, a rhythm, and a dynamic (volume)
#[derive(Debug, Clone, PartialEq)]
pub struct Note {
/// the rhythm value is a floating point value of a beat (no maximum).
/// Some defaults are available in the rhythms_constants submodule.
rhythm: f64,
/// the pitch must be between 0 and 127 (included)
pitch: u7,
/// the dynamic describes the volume of a note. Some defaults are available
/// in the dynamics_constants submodule
dynamic: u7,
}
/// Represents a note by name without a specific octave or accidental
/// Supports both letters from A to G and traditional Do Re Mi ... names
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NoteName {
Do = 0,
Re = 2,
Mi = 4,
Fa = 5,
Sol = 7,
La = 9,
Si = 11,
}
impl NoteName {
pub const C: NoteName = NoteName::Do;
pub const D: NoteName = NoteName::Re;
pub const E: NoteName = NoteName::Mi;
pub const F: NoteName = NoteName::Fa;
pub const G: NoteName = NoteName::Sol;
pub const A: NoteName = NoteName::La;
pub const B: NoteName = NoteName::Si;
}
/// Represents a note accidental
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Accidental {
Flat,
Natural,
Sharp,
}
impl Note {
/// Returns a Note with the given rhythm, pitch, and dynamic
///
/// # Arguments
///
/// * `pitch` - The pitch of the note (between 0 and 127)
/// * `rhythm` - The rhythm value of the note
/// * `dynamic` - The dynamic (volume) of the note
///
/// # Errors
///
/// * `Error::Note(Invalid::Rhythm)` if rhythm is below `0.000_001`
pub fn new(pitch: u7, rhythm: f64, dynamic: u7) -> Result<Note> {
if rhythm < 0.000_001 {
return Err(NoteError::InvalidRhythm(rhythm).into());
}
Ok(Note {
pitch,
rhythm,
dynamic,
})
}
/// Creates an iterator of notes with idential rhythms and dynamic which can be added directly
/// to a phrase using `phrase.add_sequential_notes` to be played sequentially or collected as
/// a vector to use in a `Chord`.
pub fn new_sequence<'a, PitchIter: IntoIterator<Item = u7> + 'a>(
rhythm: f64,
dynamic: u7,
pitches: PitchIter,
) -> impl std::iter::Iterator<Item = Result<Note>> + 'a {
pitches
.into_iter()
.map(move |p| Note::new(p, rhythm, dynamic))
}
/// Returns the pitch of the note
pub fn pitch(&self) -> u7 {
self.pitch
}
/// Returns the rhythm value of the note
pub fn rhythm(&self) -> f64 {
self.rhythm
}
/// Returns the dynamic value of the note
pub fn dynamic(&self) -> u7 {
self.dynamic
}
/// Returns the note name, accidental, and octave of the `Note`'s pitch
///
/// # Arguments
///
/// * `pitch`: pitch to analyse
/// * `sharps`: specifies if an accidental should be returned as a sharp
/// (if false, an accidentals will be returned as a flat). This does not
/// affect naturals.
pub fn pitch_info(&self, sharps: bool) -> (NoteName, Accidental, u8) {
pitch_info(self.pitch, sharps)
}
}
/// Returns a pitch value based on the given pitch name, octave, and accidental
///
/// # Arguments
///
/// * `letter` - The note name (between `A` and `G`)
/// * `accidental` - The accidental of the note
/// * `octave` - Which octave the note is in (`12` pitches per octave,
/// pitch `0` is a `C`, final pitch must be `127` max)
///
/// # Errors
///
/// Will return `Error::Note(Invalid::Pitch)` if final pitch is above `127`
/// or underflowed below `0`
pub fn compute_pitch(note: NoteName, accidental: Accidental, octave: u8) -> Result<u7> {
// we use u32 to avoid an uint overflow before the value check
let base_pitch = note as u32;
let nat_pitch = 12 * octave as u32 + base_pitch;
let pitch = match accidental {
Accidental::Natural => nat_pitch,
Accidental::Sharp => nat_pitch + 1,
Accidental::Flat => nat_pitch - 1,
};
if pitch > 127 {
return Err(NoteError::InvalidPitch(pitch).into());
}
Ok(u7::new(pitch as u8))
}
/// Returns the note name, accidental, and octave of the given pitch
///
/// # Arguments
///
/// * `pitch`: pitch to analyse
/// * `sharps`: specifies if an accidental should be returned as a sharp
/// (if false, an accidentals will be returned as a flat). This does not
/// affect naturals.
pub fn pitch_info(pitch: u7, sharps: bool) -> (NoteName, Accidental, u8) {
let pitch = u8::from(pitch);
let octave = pitch / 12;
let mut remainder_pitch = pitch % 12;
let mut acc = Accidental::Natural;
if matches!(remainder_pitch, 1 | 3 | 6 | 8 | 10) {
(acc, remainder_pitch) = if sharps {
(Accidental::Sharp, remainder_pitch - 1)
} else {
(Accidental::Flat, remainder_pitch + 1)
};
}
let name = match remainder_pitch {
0 => NoteName::Do,
2 => NoteName::Re,
4 => NoteName::Mi,
5 => NoteName::Fa,
7 => NoteName::Sol,
9 => NoteName::La,
11 => NoteName::Si,
_ => NoteName::Do, // This is supposedly impossible
};
(name, acc, octave)
}
#[cfg(test)]
mod tests {
use super::{compute_pitch, pitch_info, u7, Accidental, NoteName};
#[test]
fn pitch_test() {
let test_cases = vec![
(NoteName::C, Accidental::Sharp, 2, true, 25),
(NoteName::B, Accidental::Natural, 1, false, 23),
(NoteName::B, Accidental::Natural, 1, true, 23),
(NoteName::C, Accidental::Natural, 2, true, 24),
(NoteName::C, Accidental::Natural, 2, false, 24),
(NoteName::D, Accidental::Natural, 2, false, 26),
(NoteName::E, Accidental::Natural, 2, false, 28),
(NoteName::F, Accidental::Natural, 2, true, 29),
(NoteName::G, Accidental::Sharp, 5, true, 68),
(NoteName::A, Accidental::Flat, 9, false, 116),
];
let compute_only_cases = vec![
(NoteName::B, Accidental::Sharp, 1, true, 24),
(NoteName::C, Accidental::Flat, 2, false, 23),
(NoteName::E, Accidental::Sharp, 2, true, 29),
];
for (name, acc, octave, sharps, out) in test_cases {
assert_eq!(compute_pitch(name, acc, octave).unwrap(), out);
assert_eq!(pitch_info(u7::new(out), sharps), (name, acc, octave));
}
for (name, acc, octave, _, out) in compute_only_cases {
assert_eq!(compute_pitch(name, acc, octave).unwrap(), out);
}
}
}