aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFuwn <[email protected]>2023-03-06 23:18:13 +0000
committerFuwn <[email protected]>2023-03-06 23:18:13 +0000
commit6dc2f6f616dea520a6b6c52aec5a7308928d7d5b (patch)
tree00adfc06cf5d6f4f550ff57e07f74a3f19af4980 /src
downloadguitar-0.1.1.tar.xz
guitar-0.1.1.zip
feat: core featuresv0.1.1
Diffstat (limited to 'src')
-rw-r--r--src/convert.rs292
-rw-r--r--src/fretboard.rs57
-rw-r--r--src/lib.rs45
-rw-r--r--src/note.rs62
-rw-r--r--src/notes.rs37
-rw-r--r--src/pitch.rs270
-rw-r--r--src/string.rs96
-rw-r--r--src/unit.rs32
-rw-r--r--src/utility.rs40
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
+}