use serde::{Deserialize, Serialize}; use crate::Chord; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChordPosition { pub offset: usize, pub chord: Chord, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LyricLine { pub text: String, pub chords: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SectionKind { Verse, Chorus, Bridge, PreChorus, Intro, Outro, Break, Tab, Other(String), } impl SectionKind { pub fn from_label(s: &str) -> Self { match s.to_lowercase().replace(['-', '_', ' '], "").as_str() { "verse" => Self::Verse, "chorus" => Self::Chorus, "bridge" => Self::Bridge, "prechorus" => Self::PreChorus, "intro" => Self::Intro, "outro" => Self::Outro, "break" => Self::Break, "tab" => Self::Tab, _ => Self::Other(s.to_string()), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Section { pub kind: SectionKind, pub label: Option, pub lines: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SongMeta { pub title: String, pub artist: String, pub capo: Option, pub original_key: Option, pub tuning: Option, pub tempo: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Song { pub meta: SongMeta, pub sections: Vec
, } use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StoredSong { pub id: Uuid, pub song: Song, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SongSummary { pub id: Uuid, pub meta: SongMeta, pub preview_chords: Vec, } pub fn song_preview_chords(song: &Song) -> Vec { let mut seen = std::collections::HashSet::new(); let mut result = Vec::new(); 'outer: for section in &song.sections { for line in §ion.lines { for cp in &line.chords { let name = cp.chord.name(true); if seen.insert(name.clone()) { result.push(name); if result.len() >= 5 { break 'outer; } } } } } result } #[cfg(test)] mod tests { use super::*; use crate::{Chord, Note}; #[test] fn lyric_line_chord_positions() { let line = LyricLine { text: "A drop in the ocean".into(), chords: vec![ ChordPosition { offset: 0, chord: Chord { root: Note::E, descriptor: Some("m".into()) } }, ChordPosition { offset: 8, chord: Chord { root: Note::C, descriptor: None } }, ], }; assert_eq!(line.chords[0].offset, 0); assert_eq!(line.chords[1].offset, 8); } #[test] fn section_kind_from_label() { assert_eq!(SectionKind::from_label("Chorus"), SectionKind::Chorus); assert_eq!(SectionKind::from_label("Pre-Chorus"), SectionKind::PreChorus); assert_eq!(SectionKind::from_label("Tab"), SectionKind::Tab); assert_eq!(SectionKind::from_label("Riff"), SectionKind::Other("Riff".into())); } }