#![doc = include_str!("../README.md")]
pub mod cdnz_serde;
pub mod lilypond;
pub mod upgrade;
pub use cdnz_serde::VersionInfo;
use cdnz_serde::*;
use serde_with::skip_serializing_none;
use std::collections::{BTreeMap, HashMap};
use num::Rational32;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Cdnz {
pub cdnz: Metadata,
pub global: GlobalData,
pub parts: HashMap<String, Part>,
pub books: Vec<Book>,
}
impl ToString for Cdnz {
fn to_string(&self) -> String {
format!("{:#?}", self)
}
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Metadata {
pub score_title: String,
pub composer: PersonInfo,
pub arranger: PersonInfo,
pub engraver: PersonInfo,
pub description: String,
pub music_license: String,
pub engraving_license: String,
}
#[derive(Debug, Serialize, Deserialize, Default)]
#[skip_serializing_none]
pub struct PersonInfo {
pub name: String,
pub email: Option<String>,
pub is_person: bool,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GlobalData {
#[serde(
serialize_with = "serialize_position_map",
deserialize_with = "deserialize_position_map"
)]
pub modifier_events: BTreeMap<Position, Vec<GlobalModEvent>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum GlobalModEvent {
KeyChange {
note: Pitch,
mode: KeyMode,
},
TimeChange {
count: u16,
unit: u16,
},
TempoChange {
bpm: Bpm,
display_tempo: bool,
label: Option<String>,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub enum KeyMode {
Major,
Minor,
Dorian,
Phrygian,
Lydian,
Mixolydian,
Locrian,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Bpm {
pub unit: Rational32,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Part {
pub rhythmic_events: BTreeMap<Position, RhythmicEvent>,
pub modifier_events: BTreeMap<Position, Vec<LocalModEvent>>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum RhythmicEvent {
Note { pitch: Pitch, duration: Duration },
Rest { duration: Duration },
}
#[derive(Debug, Serialize, Deserialize)]
pub enum LocalModEvent {
ClefChange {
sign: ClefSign,
pos: i32,
octave: i32,
},
}
impl LocalModEvent {
pub fn new_treble_clef() -> LocalModEvent {
LocalModEvent::ClefChange {
sign: ClefSign::G,
pos: -2,
octave: 0,
}
}
pub fn new_bass_clef() -> LocalModEvent {
LocalModEvent::ClefChange {
sign: ClefSign::F,
pos: 2,
octave: 0,
}
}
pub fn new_alto_clef() -> LocalModEvent {
LocalModEvent::ClefChange {
sign: ClefSign::C,
pos: 0,
octave: 0,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ClefSign {
G,
F,
C,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Book {
pub label: String,
pub header: Header,
pub layout: Layout,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Header {}
#[derive(Debug, Serialize, Deserialize)]
pub enum Layout {
Staff(Staff),
StaffGroup(StaffGroup),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Staff {
pub parts: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StaffGroup {
pub children: Vec<Layout>,
}
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Position {
pub measure: u32,
pub pos: Rational32,
pub grace_index: u32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Pitch {
pub step: i32,
pub alteration: Rational32,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Duration {
pub base: i16,
pub dots: u16,
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_cdnz() -> Cdnz {
Cdnz {
cdnz: Metadata {
score_title: "Symphony No. 5".to_string(),
composer: PersonInfo {
name: "Ludwig van Beethoven".to_string(),
email: None,
is_person: true,
},
arranger: PersonInfo {
name: "Twilit Jack".to_string(),
email: Some("twilit.jack@proton.me".to_string()),
is_person: true,
},
engraver: PersonInfo {
name: "Twilit Jack".to_string(),
email: Some("twilit.jack@proton.me".to_string()),
is_person: true,
},
description: "Symphony No. 5 in C minor of Ludwig van Beethoven, Op. 67, written \
between 1804 and 1808.\n\n\
It is one of the best-known compositions in classical music."
.to_string(),
music_license: "Public-Domain".to_string(),
engraving_license: "CC0-1.0".to_string(),
},
global: GlobalData {
modifier_events: BTreeMap::from([(
Position {
measure: 0,
pos: Rational32::default(),
grace_index: 0,
},
Vec::from([
GlobalModEvent::KeyChange {
note: Pitch {
step: 0,
alteration: Rational32::default(),
},
mode: KeyMode::Minor,
},
GlobalModEvent::TimeChange { count: 2, unit: 4 },
]),
)]),
},
parts: HashMap::from([(
"piano".to_string(),
Part {
rhythmic_events: BTreeMap::from([
(
Position {
measure: 0,
pos: Rational32::default(),
grace_index: 0,
},
RhythmicEvent::Rest {
duration: Duration { base: 3, dots: 0 },
},
),
(
Position {
measure: 0,
pos: Rational32::new(1, 4),
grace_index: 0,
},
RhythmicEvent::Note {
pitch: Pitch {
step: 4,
alteration: Rational32::default(),
},
duration: Duration { base: 3, dots: 0 },
},
),
(
Position {
measure: 0,
pos: Rational32::new(2, 4),
grace_index: 0,
},
RhythmicEvent::Note {
pitch: Pitch {
step: 4,
alteration: Rational32::default(),
},
duration: Duration { base: 3, dots: 0 },
},
),
(
Position {
measure: 0,
pos: Rational32::new(3, 4),
grace_index: 0,
},
RhythmicEvent::Note {
pitch: Pitch {
step: 4,
alteration: Rational32::default(),
},
duration: Duration { base: 3, dots: 0 },
},
),
(
Position {
measure: 1,
pos: Rational32::default(),
grace_index: 0,
},
RhythmicEvent::Note {
pitch: Pitch {
step: 2,
alteration: Rational32::new(-1, 2),
},
duration: Duration { base: 0, dots: 0 },
},
),
]),
modifier_events: BTreeMap::from([]),
},
)]),
books: Vec::from([Book {
label: "Final Score".to_string(),
header: Header {},
layout: Layout::Staff(Staff {
parts: Vec::from(["piano".to_string()]),
}),
}]),
}
}
#[test]
fn test_round_trip() {
let original = create_test_cdnz();
let serialized = original.serialize().expect("Serialization failed");
let deserialized = Cdnz::deserialize(&serialized).expect("Deserialization failed");
assert_eq!(format!("{:?}", original), format!("{:?}", deserialized));
}
#[test]
fn test_view_json() {
let original = create_test_cdnz();
let json = serde_json::to_string_pretty(&original).expect("JSON serialization failed");
println!("{}", json);
}
}