123 lines
3.2 KiB
Rust
123 lines
3.2 KiB
Rust
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<ChordPosition>,
|
|
}
|
|
|
|
#[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<String>,
|
|
pub lines: Vec<LyricLine>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SongMeta {
|
|
pub title: String,
|
|
pub artist: String,
|
|
pub capo: Option<u8>,
|
|
pub original_key: Option<String>,
|
|
pub tuning: Option<String>,
|
|
pub tempo: Option<u16>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Song {
|
|
pub meta: SongMeta,
|
|
pub sections: Vec<Section>,
|
|
}
|
|
|
|
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<String>,
|
|
}
|
|
|
|
pub fn song_preview_chords(song: &Song) -> Vec<String> {
|
|
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()));
|
|
}
|
|
}
|