diff options
| author | Fuwn <[email protected]> | 2023-03-06 23:18:13 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2023-03-06 23:18:13 +0000 |
| commit | 6dc2f6f616dea520a6b6c52aec5a7308928d7d5b (patch) | |
| tree | 00adfc06cf5d6f4f550ff57e07f74a3f19af4980 /src | |
| download | guitar-0.1.1.tar.xz guitar-0.1.1.zip | |
feat: core featuresv0.1.1
Diffstat (limited to 'src')
| -rw-r--r-- | src/convert.rs | 292 | ||||
| -rw-r--r-- | src/fretboard.rs | 57 | ||||
| -rw-r--r-- | src/lib.rs | 45 | ||||
| -rw-r--r-- | src/note.rs | 62 | ||||
| -rw-r--r-- | src/notes.rs | 37 | ||||
| -rw-r--r-- | src/pitch.rs | 270 | ||||
| -rw-r--r-- | src/string.rs | 96 | ||||
| -rw-r--r-- | src/unit.rs | 32 | ||||
| -rw-r--r-- | src/utility.rs | 40 |
9 files changed, 931 insertions, 0 deletions
diff --git a/src/convert.rs b/src/convert.rs new file mode 100644 index 0000000..6713e65 --- /dev/null +++ b/src/convert.rs @@ -0,0 +1,292 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +#![allow( + clippy::cast_possible_truncation, + clippy::cast_precision_loss, + clippy::cast_sign_loss +)] + +use crate::{ + pitch::Pitch, + unit::{Cent, Frequency, MidiNote, Octave, Semitone}, + utility::capitalize_first_letter, + NOTES, +}; + +/// Finds the [`Frequency`] of pitch given the number of [`Semitone`]s above a +/// base [`Frequency`]. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!( +/// guitar::convert::semitones_to_frequency(1, 440.), +/// 466.1637615180899, +/// ); +/// ``` +#[must_use] +pub fn semitones_to_frequency( + semitones: Semitone, + base_frequency: Frequency, +) -> Frequency { + // https://pages.mtu.edu/~suits/NoteFreqCalcs.html + base_frequency * (semitones as Frequency / 12.).exp2() +} + +/// Find the number of [`Semitone`]s between the [`Frequency`] and a base +/// [`Frequency`]. +/// +/// The number of [`Semitone`]s is rounded to the nearest integer. If you want +/// to get the non-equal number of [`Semitone`]s, use +/// [`frequency_to_non_equal_semitones`]. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!(guitar::convert::frequency_to_semitones(466.16, 440.), 1); +/// ``` +#[must_use] +pub fn frequency_to_semitones( + frequency: Frequency, + base_frequency: Frequency, +) -> Semitone { + (12. * (frequency / base_frequency).log2()).round() as Semitone +} + +/// Finds the number of [`Semitone`]s between the [`Frequency`] and a base +/// [`Frequency`]. +/// +/// The number of [`Semitone`]s is rounded to the nearest integer. This is +/// useful for calculating the [`Frequency`] of a pitch with a non-equal +/// temperament. If you want to get the equal number of [`Semitone`]s, use +/// [`frequency_to_semitones`]. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!( +/// guitar::convert::frequency_to_non_equal_semitones(466.1637615180899, 440.), +/// 1.0000000000000009, +/// ); +/// ``` +#[must_use] +pub fn frequency_to_non_equal_semitones( + frequency: Frequency, + base_frequency: Frequency, +) -> crate::unit::NonEqualSemitone { + 12. * (frequency / base_frequency).log2() +} + +/// Finds the [`Octave`] of a [`Frequency`], with respect to a base +/// [`Frequency`]. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!( +/// guitar::convert::frequency_to_octave(466.16, 440.), +/// 4, +/// ); +#[must_use] +pub fn frequency_to_octave( + frequency: Frequency, + base_frequency: Frequency, +) -> Octave { + let midi_note = frequency_to_midi_note(frequency, base_frequency); + let octave = ({ midi_note as f64 } / 12.).floor() - 1.; + + octave as Octave +} + +/// Finds the MIDI note of a [`Frequency`], with respect to a base +/// [`Frequency`]. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!( +/// guitar::convert::frequency_to_midi_note(466.16, 440.), +/// 70, +/// ); +#[must_use] +pub fn frequency_to_midi_note( + frequency: Frequency, + base_frequency: Frequency, +) -> MidiNote { + let a4_midi_note = 69; + let semitones = frequency_to_semitones(frequency, base_frequency); + let midi_note = a4_midi_note + semitones; + + midi_note as MidiNote +} + +/// Find the number of [`Cent`]s of a pitch given the number of [`Semitone`]s +/// above any base [`Frequency`]. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!(guitar::convert::semitones_to_cents(1), 100.); +/// ``` +#[must_use] +pub fn semitones_to_cents(semitones: Semitone) -> Cent { + semitones as f64 * 100. +} + +/// Find the number of [`Semitone`]s of a pitch given the number of [`Cent`]s. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!(guitar::convert::cents_to_semitones(100.), 1); +/// ``` +#[must_use] +pub fn cents_to_semitones(cents: Cent) -> Semitone { + (cents / 100.) as Semitone +} + +/// Finds the [`Cent`]s between two [`Frequency`]s. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!( +/// guitar::convert::cents_between_frequencies(466.1637615180899, 440.), +/// 100.00000000000007, +/// ); +#[must_use] +pub fn cents_between_frequencies( + second_frequency: Frequency, + first_frequency: Frequency, +) -> Cent { + 1200. * (second_frequency / first_frequency).log2() +} + +/// Finds the enharmonic of a pitch (as a string), if it exists. +#[must_use] +pub fn enharmonic(pitch: &str) -> Option<String> { + let pitch = capitalize_first_letter(pitch); + let enharmonic_note; + let natural_note = if pitch.ends_with('b') || pitch.ends_with('#') { + &pitch[..1] + } else { + return None; + }; + // This `unwrap` *should* never fail... + let note_index = NOTES.iter().position(|&n| n == natural_note).unwrap_or(0); + let enharmonic_index; + + if pitch.ends_with('#') { + enharmonic_index = (note_index + 1) % 12; + + enharmonic_note = NOTES[enharmonic_index + 1].to_string() + "b"; + } else if pitch.ends_with('b') { + enharmonic_index = (note_index - 1) % 12; + + enharmonic_note = NOTES[enharmonic_index - 1].to_string() + "#"; + } else { + return None; + }; + + Some(enharmonic_note) +} + +/// Find the number of [`Semitone`]s above any base [`Frequency`] given a +/// pitch. +#[must_use] +pub fn pitch_and_octave_to_semitones(pitch: &str, octave: Octave) -> Semitone { + let pitch = capitalize_first_letter(pitch); + let pitch_offset = + i64::from(*crate::notes::NOTES_OFFSET.get(&pitch).unwrap_or(&0)); + let semitones = pitch_offset + (octave - 4) * 12; + + semitones as Semitone +} + +/// Encapsulate a pitch as a [`Pitch`] given the [`Frequency`] and a base +/// [`Frequency`]. +/// +/// # Examples +/// +/// ```rust +/// assert_eq!( +/// guitar::convert::frequency_to_note(466.16, 440., 69).semitones(), +/// guitar::Pitch::new("A#", 4).semitones(), +/// ); +#[must_use] +pub fn frequency_to_pitch( + frequency: Frequency, + base_frequency: Frequency, + base_midi_note: MidiNote, +) -> Pitch { + let semitones_above_a4 = 12. * (frequency / base_frequency).log2(); + let nearest_midi_pitch = semitones_above_a4.round(); + let midi_pitch = nearest_midi_pitch + base_midi_note as f64; + let octave = (midi_pitch / 12.).floor() - 1.; + let pitch = match (midi_pitch % 12.) as u64 { + 0 => "C", + 1 => "C#", + 2 => "D", + 3 => "D#", + 4 => "E", + 5 => "F", + 6 => "F#", + 7 => "G", + 8 => "G#", + 9 => "A", + 10 => "A#", + 11 => "B", + _ => unreachable!(), + }; + + Pitch::new(pitch, octave as Octave) +} + +/// Finds the [`Frequency`] of a [`MidiNote`] with respect to a base +/// [`Frequency`] and base [`MidiNote`]. +#[must_use] +pub fn midi_note_to_frequency( + midi_note: MidiNote, + base_frequency: Frequency, + base_midi_note: MidiNote, +) -> Frequency { + base_frequency * ((midi_note - base_midi_note) as f64 / 12.).exp2() +} + +/// Finds the [`Frequency`] of a pitch and its [`Octave`], with respect to a +/// base [`Frequency`] and base [`MidiNote`]. +#[must_use] +pub fn pitch_and_octave_to_frequency( + pitch: &str, + octave: Octave, + base_frequency: Frequency, + base_midi_note: MidiNote, +) -> Frequency { + let pitch = capitalize_first_letter(pitch); + // This used to work, but sometime when I moved the [`Pitch`] related things + // out of [`Note`], it stopped working. I messed around and after a few + // seconds I realized that the now working solution, under it, works... + // I'm not sure why, but I'm not going to question it. + + // let semitones_above_a4 = + // pitch_and_octave_to_semitones(pitch, octave) + (octave - 4) * 12; + let semitones_above_a4 = pitch_and_octave_to_semitones(&pitch, octave); + let midi_pitch = semitones_above_a4 + base_midi_note; + + midi_note_to_frequency(midi_pitch as MidiNote, base_frequency, base_midi_note) +} diff --git a/src/fretboard.rs b/src/fretboard.rs new file mode 100644 index 0000000..65583e9 --- /dev/null +++ b/src/fretboard.rs @@ -0,0 +1,57 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{string::String, unit::Frets, Pitch}; + +pub struct Fretboard { + strings: Vec<String>, + frets: Frets, +} + +impl Fretboard { + /// Create a new [`Fretboard`] from a [`Vec`] of [`String`]s and a number of + /// [`Frets`]. + #[must_use] + pub fn new_from_strings(strings: Vec<String>, frets: Frets) -> Self { + Self { strings, frets } + } + + /// Create a new [`Fretboard`] from a number of [`Frets`], using standard + /// tuning for a six-string guitar (E2, A2, D3, G3, B3, E4). + #[must_use] + pub fn new(frets: Frets) -> Self { + Self { + strings: vec![ + String::new(Pitch::new("E", 2), frets), + String::new(Pitch::new("A", 2), frets), + String::new(Pitch::new("D", 3), frets), + String::new(Pitch::new("G", 3), frets), + String::new(Pitch::new("B", 3), frets), + String::new(Pitch::new("E", 4), frets), + ], + frets, + } + } + + /// Return the [`Vec`] of [`String`]s. + #[must_use] + pub const fn strings(&self) -> &Vec<String> { &self.strings } + + /// Return the number of [`Frets`]. + #[must_use] + pub const fn frets(&self) -> &Frets { &self.frets } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8b40bc6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,45 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +#![deny( + warnings, + nonstandard_style, + unused, + future_incompatible, + rust_2018_idioms, + clippy::all, + clippy::nursery, + clippy::pedantic +)] +#![recursion_limit = "128"] +#![doc = include_str!("../README.md")] + +// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + +pub mod convert; +pub mod fretboard; +pub mod note; +pub mod notes; +pub mod pitch; +pub mod string; +pub mod unit; +pub mod utility; + +pub use fretboard::Fretboard; +pub use note::Note; +pub use notes::NOTES; +pub use pitch::Pitch; diff --git a/src/note.rs b/src/note.rs new file mode 100644 index 0000000..053ce15 --- /dev/null +++ b/src/note.rs @@ -0,0 +1,62 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{pitch::Pitch, unit::Octave}; + +/// A note which only keeps track of its frequency, octave, and name; enables +/// for a more flexible note system; including conversions, enharmonics, and +/// more. +/// +/// # Examples +/// +/// ```rust +/// let _ = guitar::Note::new("A", 4); +/// ``` +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Note { + pitch: Pitch, +} + +impl Note { + /// Creates a new, specific [`Note`] given a note's name, octave, and + /// duration. + /// + /// # Examples + /// + /// ```rust + /// let _ = guitar::Note::new("A", 4); + /// ``` + #[must_use] + pub fn new(pitch: &str, octave: Octave) -> Self { + let pitch = Pitch::new(pitch, octave); + + Self::new_from_pitch(pitch) + } + + /// Creates a new, specific [`Note`] given a frequency and a [`Pitch`]. + /// + /// This is an internal command which is made obsolete by the other, + /// public-acing `new_from_*` methods. + #[must_use] + pub const fn new_from_pitch(pitch: Pitch) -> Self { Self { pitch } } + + /// Returns the [`Pitch`] of the [`Note`] for further manipulation. + pub fn pitch(&mut self) -> &Pitch { &self.pitch } + + /// Returns the [`Pitch`] of the [`Note`] for further manipulation. + pub fn pitch_mut(&mut self) -> &mut Pitch { &mut self.pitch } +} diff --git a/src/notes.rs b/src/notes.rs new file mode 100644 index 0000000..7390dcc --- /dev/null +++ b/src/notes.rs @@ -0,0 +1,37 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use std::collections::HashMap; + +use once_cell::sync::Lazy; + +pub const NOTES: [&str; 12] = [ + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", +]; + +pub static NOTES_OFFSET: Lazy<HashMap<String, i8>> = Lazy::new(|| { + let mut notes = HashMap::new(); + let mut i = -9; + + for note in NOTES { + notes.insert(note.to_string(), i); + + i += 1; + } + + notes +}); diff --git a/src/pitch.rs b/src/pitch.rs new file mode 100644 index 0000000..43aa035 --- /dev/null +++ b/src/pitch.rs @@ -0,0 +1,270 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{ + convert, + convert::{frequency_to_pitch, semitones_to_frequency}, + unit::{Cent, Frequency, MidiNote, Octave, Semitone}, + utility::capitalize_first_letter, +}; + +/// A structure which represents the [`Pitch`] of a [`Note`]. +/// +/// # Examples +/// +/// ```rust +/// let _ = guitar::Pitch::new("C", 4); +/// ``` +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Pitch { + pitch: String, + frequency: Frequency, + base_frequency: Frequency, + base_midi_note: MidiNote, + octave: Octave, +} + +impl Pitch { + /// Creates a new [`Pitch`] from the name of a [`Pitch`]. + /// + /// # Examples + /// + /// ```rust + /// let _ = guitar::Pitch::new("C", 4); + /// ``` + #[must_use] + pub fn new(pitch: &str, octave: Octave) -> Self { + let mut pitch = capitalize_first_letter(pitch); + + if pitch.ends_with('b') { + if let Some(enharmonic) = convert::enharmonic(&pitch) { + pitch = enharmonic; + } + } + + Self { + pitch: pitch.to_string(), + frequency: convert::pitch_and_octave_to_frequency( + &pitch, octave, 440., 69, + ), + base_frequency: 440., + base_midi_note: 69, + octave, + } + } + + /// Creates a new [`Pitch`] given all parameters. + /// + /// Be careful when using this function, as it doesn't check if the given + /// [`Pitch`] is valid. + #[must_use] + pub fn new_from_complete( + pitch: &str, + frequency: Frequency, + base_frequency: Frequency, + base_midi_note: MidiNote, + octave: Octave, + ) -> Self { + Self { + pitch: pitch.to_string(), + frequency, + base_frequency, + base_midi_note, + octave, + } + } + + /// Creates a new [`Pitch`] with + #[must_use] + pub fn new_from_builder( + pitch: Option<&str>, + frequency: Option<Frequency>, + base_frequency: Frequency, + base_midi_note: MidiNote, + octave: Option<Octave>, + ) -> Self { + if let (Some(pitch), Some(_), Some(octave)) = (pitch, frequency, octave) { + Self::new(pitch, octave) + } else if let (Some(pitch), Some(octave)) = (pitch, octave) { + Self { + pitch: pitch.to_string(), + frequency: convert::pitch_and_octave_to_frequency( + pitch, + octave, + base_frequency, + base_midi_note, + ), + base_frequency, + base_midi_note, + octave, + } + } else if let Some(frequency) = frequency { + frequency_to_pitch(frequency, base_frequency, base_midi_note) + } else { + Self::new("A", 4) + } + } + + /// Creates a new, specific [`Note`] given the note's frequency and + /// duration. + /// # Examples + /// + /// ```rust + /// let _ = guitar::Pitch::new_from_frequency(493.88); + /// ``` + #[must_use] + pub fn new_from_frequency(frequency: Frequency) -> Self { + frequency_to_pitch(frequency, 440., 69) + } + + /// Creates a new [`Pitch`] from the number of [`Semitone`]s above the + /// default base frequency of 440 Hz. + /// + /// # Examples + /// + /// ```rust + /// let _ = guitar::Pitch::new_from_semitones(0); + /// ``` + #[must_use] + pub fn new_from_semitones(semitones: Semitone) -> Self { + let frequency = semitones_to_frequency(semitones, 440.); + + frequency_to_pitch(frequency, 440., 69) + } + + /// Returns the pitch of the [`Pitch`]. + #[must_use] + pub fn pitch(&self) -> &str { &self.pitch } + + /// Sets the name of the [`Pitch`]. + pub fn set_pitch(&mut self, pitch: &str) { + self.pitch = capitalize_first_letter(pitch); + self.frequency = convert::pitch_and_octave_to_frequency( + &self.pitch, + self.octave, + self.base_frequency, + self.base_midi_note, + ); + } + + /// Returns the [`Frequency`] of the [`Note`]. + #[must_use] + pub const fn frequency(&self) -> Frequency { self.frequency } + + /// Sets the [`Note`]'s [`Frequency`]. + pub fn set_frequency(&mut self, frequency: Frequency) { + self.frequency = frequency; + self.octave = convert::frequency_to_octave(frequency, self.base_frequency); + self.pitch = + frequency_to_pitch(frequency, self.base_frequency, self.base_midi_note) + .pitch() + .to_string(); + } + + /// Returns the [`Note`]'s base [`Frequency`]. + #[must_use] + pub const fn base_frequency(&self) -> Frequency { self.base_frequency } + + /// Sets the [`Note`]'s base [`Frequency`]. + pub fn set_base_frequency(&mut self, frequency: Frequency) { + self.base_frequency = frequency; + self.base_midi_note = + convert::frequency_to_midi_note(frequency, self.base_frequency); + self.frequency = semitones_to_frequency(self.semitones(), frequency); + } + + /// Returns the [`Note`]'s base MIDI note. + #[must_use] + pub const fn base_midi_note(&self) -> MidiNote { self.base_midi_note } + + /// Sets the [`Note`]'s base MIDI note. + pub fn set_base_midi_note(&mut self, midi_note: MidiNote) { + self.base_midi_note = midi_note; + self.base_frequency = convert::midi_note_to_frequency( + midi_note, + self.base_frequency, + self.base_midi_note, + ); + self.frequency = + semitones_to_frequency(self.semitones(), self.base_frequency); + } + + /// Returns the [`Octave`] of the [`Note`]. + #[must_use] + pub const fn octave(&self) -> Octave { self.octave } + + /// Sets the [`Note`]'s [`Octave`]. + pub fn set_octave(&mut self, octave: Octave) { + self.octave = octave; + self.frequency = convert::pitch_and_octave_to_frequency( + &self.pitch, + self.octave, + self.base_frequency, + self.base_midi_note, + ); + } + + /// Returns the [`Note`]'s enharmonic pair, if it exists. + #[must_use] + pub fn enharmonic(&self) -> Option<Self> { + let enharmonic = convert::enharmonic(&self.pitch); + + enharmonic.map(|enharmonic| { + Self::new_from_complete( + &enharmonic, + self.frequency, + self.base_frequency, + self.base_midi_note, + self.octave, + ) + }) + } + + /// Returns the number of [`Semitone`]s that the [`Note`] is away from the + /// base frequency. + #[must_use] + pub fn semitones(&self) -> Semitone { + convert::frequency_to_semitones(self.frequency, self.base_frequency) + } + + /// Sets the number of [`Semitone`]s that the [`Note`]'s is away from the base + /// frequency. + pub fn set_semitones(&mut self, semitones: Semitone) { + self.frequency = semitones_to_frequency(semitones, self.base_frequency); + self.octave = + convert::frequency_to_octave(self.frequency, self.base_frequency); + self.pitch = frequency_to_pitch( + self.frequency, + self.base_frequency, + self.base_midi_note, + ) + .pitch() + .to_string(); + } + + /// Returns the [`Note`]'s representation as a MIDI note. + #[must_use] + pub fn midi_note(&self) -> MidiNote { + convert::frequency_to_midi_note(self.frequency, self.base_frequency) + } + + /// Returns the [`Note`]'s representation in [`Cent`]s. + #[must_use] + pub fn cents(&self) -> Cent { + convert::semitones_to_cents(self.semitones()) as Cent + } +} diff --git a/src/string.rs b/src/string.rs new file mode 100644 index 0000000..26e562c --- /dev/null +++ b/src/string.rs @@ -0,0 +1,96 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +use crate::{unit::Frequency, Pitch}; + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct String { + pitch: Pitch, + frets: Vec<Pitch>, + fret_count: usize, + base_frequency: Frequency, +} + +impl String { + #[must_use] + pub fn new(pitch: Pitch, fret_count: usize) -> Self { + let mut frets = vec![]; + let mut next_pitch = pitch.clone(); + + for _ in 0..fret_count { + let semitones = next_pitch.semitones(); + + frets.push(next_pitch.clone()); + next_pitch.set_semitones(semitones + 1); + } + + Self { + pitch, + frets, + fret_count, + base_frequency: 440., + } + } + + #[must_use] + pub const fn pitch(&self) -> &Pitch { &self.pitch } + + #[must_use] + pub const fn frets(&self) -> &Vec<Pitch> { &self.frets } + + #[must_use] + pub const fn fret_count(&self) -> &usize { &self.fret_count } + + #[must_use] + pub const fn base_frequency(&self) -> &Frequency { &self.base_frequency } + + pub fn set_pitch(&mut self, pitch: Pitch) { + let mut next_pitch = pitch.clone(); + + for fret in &mut self.frets { + let semitones = next_pitch.semitones(); + + *fret = next_pitch.clone(); + next_pitch.set_semitones(semitones + 1); + } + + self.pitch = pitch; + } + + pub fn set_fret_count(&mut self, fret_count: usize) { + let mut frets = vec![]; + let mut next_pitch = self.pitch.clone(); + + for _ in 0..fret_count { + let semitones = next_pitch.semitones(); + + frets.push(next_pitch.clone()); + next_pitch.set_semitones(semitones + 1); + } + + self.frets = frets; + self.fret_count = fret_count; + } + + pub fn set_base_frequency(&mut self, base_frequency: Frequency) { + self.base_frequency = base_frequency; + + for fret in &mut self.frets { + fret.set_base_frequency(base_frequency); + } + } +} diff --git a/src/unit.rs b/src/unit.rs new file mode 100644 index 0000000..7883431 --- /dev/null +++ b/src/unit.rs @@ -0,0 +1,32 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +/// The internal data type of an octave. +pub type Octave = i64; +/// The internal data type of a frequency. +pub type Frequency = f64; +/// The internal data type of a semitone. +pub type Semitone = i64; +/// The internal data type of a non-equal semitone (e.g., non-equal +/// temperament). +pub type NonEqualSemitone = f64; +/// The internal data type of a MIDI note. +pub type MidiNote = i64; +/// The internal data type of cents. +pub type Cent = f64; +/// The internal data type of a count of frets. +pub type Frets = usize; diff --git a/src/utility.rs b/src/utility.rs new file mode 100644 index 0000000..3f7850b --- /dev/null +++ b/src/utility.rs @@ -0,0 +1,40 @@ +// This file is part of Guitar <https://github.com/Fuwn/guitar>. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +// Copyright (C) 2022-2022 Fuwn <[email protected]> +// SPDX-License-Identifier: GPL-3.0-only + +// https://pages.mtu.edu/~suits/notefreqs.html +pub const SPEED_OF_SOUND_METRES_PER_SECOND: f64 = 345.0; +pub const SPEED_OF_SOUND_FEET_PER_SECOND: f64 = 1130.33; +pub const SPEED_OF_SOUND_MILES_PER_HOUR: f64 = 770.0; + +/// Capitalize the first letter of a string. +/// +/// This is used internally to make sure that pitch names are capitalized. +pub fn capitalize_first_letter(string: &str) -> String { + let mut characters = string.chars(); + + characters.next().map_or_else(String::new, |f| { + f.to_uppercase().collect::<String>() + characters.as_str() + }) +} + +#[must_use] +pub fn centimeters_to_inches(centimeters: f64) -> f64 { centimeters / 2.54 } + +#[must_use] +pub fn frequency_to_wavelength_cm(frequency: f64) -> f64 { + SPEED_OF_SOUND_METRES_PER_SECOND / frequency * 100.0 +} |