1use crate::song::{Chunk, Song};
4
5use crate::chords::Chord;
6
7pub struct Transposer {
8 s: i8,
9}
10
11pub fn map_to_chords<'a, F>(song: &mut Song, f: F)
12where
13 F: Fn(&mut Chord),
14{
15 for section in song.iter_mut() {
16 for line in section.iter_mut() {
17 for chunk in line.iter_mut() {
18 match chunk {
19 Chunk::Chord(c) => f(c),
20 _ => {}
21 }
22 }
23 }
24 }
25}
26
27impl Transposer {
28 pub fn new(semitones: i8) -> Self {
31 Self { s: semitones }
32 }
33
34 pub fn apply_transpose(&self, song: &mut Song) {
36 if self.s.abs() % 12 != 0 {
37 map_to_chords(song, |chord| {
38 chord.root = chord.root + self.s;
39 chord.bass = chord.bass + self.s;
40 });
41 }
42 }
43
44 pub fn transpose(&self, song: Song) -> Song {
46 let mut song = song;
47 self.apply_transpose(&mut song);
48 song
49 }
50}
51
52#[cfg(test)]
53mod test {
54
55 #[test]
56 fn transpose() {
57 use super::*;
58 use crate::chords::Note;
59 use crate::song::{Line, Song};
60 use std::str::FromStr;
61 let song = Song::from_str("[C]A [D#m]Cm").expect("Failed to parse song");
62 let song2 = Transposer::new(-3).transpose(song);
63
64 let line = song2.iter().nth(0).unwrap().iter().nth(0).unwrap();
65 assert_eq!(
66 line,
67 &Line(vec![
68 Chunk::Chord(Chord::major(Note::A)),
69 Chunk::Lyrics("A ".to_string()),
70 Chunk::Chord(Chord::minor(Note::C)),
71 Chunk::Lyrics("Cm".to_string()),
72 ])
73 );
74 }
75}