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(|| Error::Tablature("list index out of range".to_string()))?;
191 let open = self.tuning[index].ps();
192 let sounding = open + note.fret.unwrap_or(0) as FloatType;
193 pitches[index] = Some(Pitch::from_number(sounding)?);
194 }
195 Ok(pitches)
196 }
197}
198
199#[derive(Clone, Debug, PartialEq, Eq)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
203pub struct FirstFret {
204 pub fret: IntegerType,
206 pub location: String,
208}
209
210impl FirstFret {
211 pub fn new(fret: IntegerType) -> Self {
213 Self {
214 fret,
215 location: "right".to_string(),
216 }
217 }
218}
219
220#[derive(Clone, Debug, PartialEq)]
222#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
223pub struct ChordWithFretBoard {
224 pub chord_symbol: ChordSymbol,
226 pub fret_board: FretBoard,
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn fret_notes_are_written_and_ordered_as_music21_does() {
236 assert_eq!(
238 FretNote::new(Some(6), Some(133), None).to_string(),
239 "6th string, 133rd fret"
240 );
241 assert_eq!(FretNote::new(None, None, None).string, None);
242 let board = FretBoard::new(
243 6,
244 vec![
245 FretNote::new(Some(1), Some(2), Some(2)),
246 FretNote::new(Some(2), Some(1), Some(1)),
247 ],
248 4,
249 );
250 let strings: Vec<Option<IntegerType>> = board
251 .fret_notes_lowest_first()
252 .iter()
253 .map(|note| note.string)
254 .collect();
255 assert_eq!(strings, [Some(2), Some(1)]);
256 assert!(board.pitches().is_err());
257 }
258
259 #[test]
260 fn a_board_sounds_its_strings_raised_by_their_frets() -> Result<()> {
261 let board = FretBoard::guitar(
263 vec![
264 FretNote::new(Some(3), Some(2), None),
265 FretNote::new(Some(2), Some(2), None),
266 FretNote::new(Some(4), Some(2), None),
267 FretNote::new(Some(5), Some(0), None),
268 ],
269 4,
270 );
271 let names: Vec<Option<String>> = board
272 .pitches()?
273 .iter()
274 .map(|pitch| pitch.as_ref().map(Pitch::name_with_octave))
275 .collect();
276 assert_eq!(
277 names,
278 [
279 None,
280 Some("A2".to_string()),
281 Some("E3".to_string()),
282 Some("A3".to_string()),
283 Some("C#4".to_string()),
284 None
285 ]
286 );
287 Ok(())
288 }
289}