1use std::fmt;
14
15use crate::{
16 chordsymbol::ChordSymbol,
17 defaults::{FloatType, IntegerType},
18 error::{Error, Result},
19 pitch::{Pitch, microtone::ordinal_suffix},
20};
21
22#[derive(Clone, Debug, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct FretNote {
27 pub string: Option<IntegerType>,
29 pub fret: Option<IntegerType>,
31 pub fingering: Option<IntegerType>,
33 pub display_finger_number: bool,
35}
36
37impl FretNote {
38 pub fn new(
40 string: Option<IntegerType>,
41 fret: Option<IntegerType>,
42 fingering: Option<IntegerType>,
43 ) -> Self {
44 Self {
45 string,
46 fret,
47 fingering,
48 display_finger_number: true,
49 }
50 }
51}
52
53impl fmt::Display for FretNote {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 let parts: Vec<String> = [
58 (self.string, "string"),
59 (self.fret, "fret"),
60 (self.fingering, "finger"),
61 ]
62 .into_iter()
63 .filter_map(|(value, what)| {
64 value.map(|value| format!("{value}{} {what}", ordinal_suffix(value)))
65 })
66 .collect();
67 f.write_str(&parts.join(", "))
68 }
69}
70
71#[derive(Clone, Debug, PartialEq)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75pub struct FretBoard {
76 pub strings: usize,
78 pub fret_notes: Vec<FretNote>,
80 pub display_frets: usize,
82 pub tuning: Vec<Pitch>,
84}
85
86fn tuned(names: &[&str]) -> Vec<Pitch> {
87 names
88 .iter()
89 .map(|name| Pitch::from_name(*name).expect("a tuning names pitches"))
90 .collect()
91}
92
93impl FretBoard {
94 pub fn new(strings: usize, fret_notes: Vec<FretNote>, display_frets: usize) -> Self {
96 Self {
97 strings,
98 fret_notes,
99 display_frets,
100 tuning: Vec::new(),
101 }
102 }
103
104 pub fn guitar(fret_notes: Vec<FretNote>, display_frets: usize) -> Self {
107 Self {
108 tuning: tuned(&["E2", "A2", "D3", "G3", "B3", "E4"]),
109 ..Self::new(6, fret_notes, display_frets)
110 }
111 }
112
113 pub fn ukulele(fret_notes: Vec<FretNote>, display_frets: usize) -> Self {
116 Self {
117 tuning: tuned(&["G4", "C4", "E4", "A4"]),
118 ..Self::new(4, fret_notes, display_frets)
119 }
120 }
121
122 pub fn bass_guitar(fret_notes: Vec<FretNote>, display_frets: usize) -> Self {
125 Self {
126 tuning: tuned(&["E1", "A1", "D2", "G2"]),
127 ..Self::new(4, fret_notes, display_frets)
128 }
129 }
130
131 pub fn mandolin(fret_notes: Vec<FretNote>, display_frets: usize) -> Self {
134 Self {
135 tuning: tuned(&["G3", "D4", "A4", "E5"]),
136 ..Self::new(4, fret_notes, display_frets)
137 }
138 }
139
140 pub fn fret_note_by_string(&self, string: IntegerType) -> Option<&FretNote> {
143 self.fret_notes
144 .iter()
145 .find(|note| note.string == Some(string))
146 }
147
148 pub fn fret_notes_lowest_first(&self) -> Vec<&FretNote> {
151 (1..=self.strings as IntegerType)
152 .rev()
153 .filter_map(|string| self.fret_note_by_string(string))
154 .collect()
155 }
156
157 pub fn pitches(&self) -> Result<Vec<Option<Pitch>>> {
169 if self.tuning.len() != self.strings {
170 return Err(Error::Tablature(format!(
171 "Tuning must be set first, tuned for {} notes, on a {} string instrument",
172 self.tuning.len(),
173 self.strings
174 )));
175 }
176 let mut pitches: Vec<Option<Pitch>> = vec![None; self.strings];
177 for note in &self.fret_notes {
178 let python_index = -i64::from(note.string.unwrap_or(0));
182 let index = if python_index < 0 {
183 python_index + self.strings as i64
184 } else {
185 python_index
186 };
187 let index = usize::try_from(index)
188 .ok()
189 .filter(|index| *index < self.strings)
190 .ok_or_else(|| {
191 Error::Tablature(format!(
192 "string {} is not one of the {} strings",
193 note.string.unwrap_or(0),
194 self.strings
195 ))
196 })?;
197 let open = self.tuning[index].ps();
198 let sounding = open + note.fret.unwrap_or(0) as FloatType;
199 pitches[index] = Some(Pitch::from_number(sounding)?);
200 }
201 Ok(pitches)
202 }
203}
204
205#[derive(Clone, Debug, PartialEq, Eq)]
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209pub struct FirstFret {
210 pub fret: IntegerType,
212 pub location: String,
214}
215
216impl FirstFret {
217 pub fn new(fret: IntegerType) -> Self {
219 Self {
220 fret,
221 location: "right".to_string(),
222 }
223 }
224}
225
226#[derive(Clone, Debug, PartialEq)]
228#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
229pub struct ChordWithFretBoard {
230 pub chord_symbol: ChordSymbol,
232 pub fret_board: FretBoard,
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn fret_notes_are_written_and_ordered_as_music21_does() {
242 assert_eq!(
244 FretNote::new(Some(6), Some(133), None).to_string(),
245 "6th string, 133rd fret"
246 );
247 assert_eq!(FretNote::new(None, None, None).string, None);
248 let board = FretBoard::new(
249 6,
250 vec![
251 FretNote::new(Some(1), Some(2), Some(2)),
252 FretNote::new(Some(2), Some(1), Some(1)),
253 ],
254 4,
255 );
256 let strings: Vec<Option<IntegerType>> = board
257 .fret_notes_lowest_first()
258 .iter()
259 .map(|note| note.string)
260 .collect();
261 assert_eq!(strings, [Some(2), Some(1)]);
262 assert!(board.pitches().is_err());
263 }
264
265 #[test]
266 fn a_board_sounds_its_strings_raised_by_their_frets() -> Result<()> {
267 let board = FretBoard::guitar(
269 vec![
270 FretNote::new(Some(3), Some(2), None),
271 FretNote::new(Some(2), Some(2), None),
272 FretNote::new(Some(4), Some(2), None),
273 FretNote::new(Some(5), Some(0), None),
274 ],
275 4,
276 );
277 let names: Vec<Option<String>> = board
278 .pitches()?
279 .iter()
280 .map(|pitch| pitch.as_ref().map(Pitch::name_with_octave))
281 .collect();
282 assert_eq!(
283 names,
284 [
285 None,
286 Some("A2".to_string()),
287 Some("E3".to_string()),
288 Some("A3".to_string()),
289 Some("C#4".to_string()),
290 None
291 ]
292 );
293 Ok(())
294 }
295}