cdnz 0.2.0

An open data format for storing music
Documentation
// SPDX-FileCopyrightText: 2026 Twilit Jack <twilit-jack@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-or-later

#![doc = include_str!("../README.md")]

pub mod cdnz_serde;
pub mod lilypond;
pub mod upgrade;

use num::Rational32 as Rat32;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use std::collections::BTreeMap;

// =========================== ROOT ===========================

/// The root object of a CDNZ file.
#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
pub struct Project {
	pub cdnz: Metadata,
	pub global: GlobalData,
	pub parts: BTreeMap<PartName, Part>,
	pub layouts: BTreeMap<LayoutName, Layout>,
}

/// Metadata about the CDNZ file in question.
#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
pub struct Metadata {
	/// The name/title for the music.
	///
	/// This is separate from the file name, because file names are ambiguous, and are often
	/// discouraged from including certain special/Unicode characters, including spaces.
	///
	/// This does not have to be the same as the title in the header of the books, but it's highly
	/// encouraged. It could be different in, for example, books with text in a different language.
	pub score_title: String,

	/// Info about the composer of the music.
	///
	/// The composer is the author of the base melody. This might also be the name of a folk, for
	/// rhapsodies or folk song arrangements.
	pub composer: PersonInfo,
	/// Info about the arranger of the music.
	///
	/// The arranger is the author of the accompanying bass and secondary melodies. This might be
	/// the same person as the composer for original works.
	pub arranger: PersonInfo,
	/// Info about the engraver of the music.
	///
	/// The engraver is the author of the exact layout and encoding of the music, i.e. the CDNZ file.
	/// This might be the same as the composer or arranger if they're doing their first work via
	/// CDNZ or Cadenza.
	pub engraver: PersonInfo,

	/// A description of the piece, typically given by the composer, including thoughts about the
	/// piece.
	pub description: String,

	/// A SPDX tag describing the licensing of the music/arrangement.
	///
	/// Can also be `Public-Domain`, which is used for expired copyright works (e.g. baroque,
	/// classical, romance works).
	pub music_license: String,
	/// A SPDX tag describing the licensing of the engraving/encoding.
	///
	/// If you want to waive your copyright, it's recommended to use `CC0-1.0` here, instead of
	/// `Public-Domain`.
	pub engraving_license: String,
}

#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
#[skip_serializing_none]
pub struct PersonInfo {
	/// The name of this person or entity.
	///
	/// See `is_person` for more info about "entity".
	pub name: String,

	/// The email through which this person or entity can be reached.
	///
	/// This is an `Option`, because some works might be composed by a historical composer, or the
	/// melody might be of a folk.
	pub email: Option<String>,

	/// Whether or not this info is about a person.
	///
	/// This may be false for bands, companies or a folk, in which case `name` doesn't mean the name
	/// of a person.
	pub is_person: bool,
}

// =========================== GLOBAL DATA ===========================

#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
pub struct GlobalData {
	pub mod_events: BTreeMap<Position, Vec<GlobalModEvent>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum GlobalModEvent {
	KeyChange {
		/// The note this key change references.
		note: Pitch,
		/// The mode this key change uses, in reference to the note e.g. `Major`.
		mode: KeyMode,
	},

	/// A TimeChange defines a time signature change, changing the beat structure.
	TimeChange {
		/// The number on the top of the key signature.
		count: u16,
		/// The number on the bottom of the key signature.
		unit: u16,
	},

	/// A TempoChange defines a change of tempo, usually a BPM change, but it can
	/// also define a rhythm, e.g. swing.
	TempoChange {
		/// The BPM value this TempoChange changes it to.
		///
		/// While you might not want to always display it, it's still required. See `display_tempo`
		/// for hiding the numeric value.
		bpm: Bpm,

		/// Whether to display the new BPM as a metronome mark or to hide it.
		display_tempo: bool,

		/// What text label to have beside the metronome mark or standalone.
		///
		/// e.g. "Allegro", "Moderato".
		label: Option<String>,
	},
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum KeyMode {
	Major,
	Minor,

	// Currently disabled due to "do I need this?".
	// Might be changed in the future, I don't know a lot about church modes myself tbh.
	//Ionian,
	//Aeolian,
	Dorian,
	Phrygian,
	Lydian,
	Mixolydian,
	Locrian,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Bpm {
	pub unit: Rat32,
}

// =========================== PART ===========================

pub type PartName = String;

#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
pub struct Part {
	pub voices: Vec<Voice>,
}

#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
pub struct Voice {
	pub instrument: Instrument,
	pub rhythmic_events: BTreeMap<Position, RhythmicEvent>,
	pub mod_events: BTreeMap<Position, Vec<LocalModEvent>>,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum RhythmicEvent {
	Note { pitches: Vec<Pitch> },
	DrumNote {},
	Rest {},
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum LocalModEvent {}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum ClefSign {
	G,
	F,
	C,
}

// =========================== LAYOUT ===========================

pub type LayoutName = String;

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Layout {
	pub header: Header,
	pub layout: LayoutElement,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Header {}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum LayoutElement {
	Staff { voices: Vec<LayoutVoice> },
	StaffGroup { children: Vec<LayoutElement> },
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct LayoutVoice {
	pub mod_events: Vec<LayoutModEvent>,
	pub referenced_voice: String,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum LayoutModEvent {
	ClefChange {
		sign: ClefSign,

		/// The staff position of the clef, in increments of half a staff line, starting at the
		/// middle line.
		///
		/// Examples:
		/// - Treble: -2
		/// - Bass: 2
		/// - Alto: 0
		pos: i32,

		/// The octave transposition for a clef.
		///
		/// Usually 0, but changes for things like a "treble_8" clef (where it would be `-8`).
		octave: i32,
	},
	Transposition {
		pitch: Pitch,
	},
}

impl LayoutModEvent {
	/// A helper function for creating a basic treble clef.
	pub fn new_treble_clef() -> LayoutModEvent {
		LayoutModEvent::ClefChange {
			sign: ClefSign::G,
			pos: -2,
			octave: 0,
		}
	}
	/// A helper function for creating a basic bass clef.
	pub fn new_bass_clef() -> LayoutModEvent {
		LayoutModEvent::ClefChange {
			sign: ClefSign::F,
			pos: 2,
			octave: 0,
		}
	}
	/// A helper function for creating a basic alto clef.
	pub fn new_alto_clef() -> LayoutModEvent {
		LayoutModEvent::ClefChange {
			sign: ClefSign::C,
			pos: 0,
			octave: 0,
		}
	}
}

// =========================== PRIMITIVES ===========================

#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Position {
	/// The measure index this position is in/is relative to.
	pub measure: u32,

	/// The position in the measure as a rational.
	///
	/// (0, 1) would be the start of the measure, (1, 2) – halfway through.
	pub pos: Rat32,

	pub grace_index: u32,
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Pitch {
	/// A whole step, beginning at middle C.
	///
	/// Examples:
	/// - C4 (middle C): 0
	/// - A4 (concert pitch): 5
	/// - C5: 7
	/// - C3: -7
	pub step: i32,

	/// Alteration of the note, with 1/1 being a whole tone.
	///
	/// Examples:
	/// - Natural: (0, 1)
	/// - Sharp: (1, 2)
	/// - Flat: (-1, 2)
	/// - Double sharp: (1, 1)
	pub alteration: Rat32,
}

#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
pub enum Instrument {
	// Basic assortment of instruments. Will be expanded as time goes.
	// You'll be able to suggest your own additions once issues get enabled.

	// ==== Strings ====
	Violin,
	Viola,
	Cello,
	DoubleBass,

	Harp,

	Guitar,
	ElectricGuitar,
	BassGuitar,
	ElectricBassGuitar,

	Banjo,
	Mandolin,
	Lute,

	// ==== Woodwinds ====
	Flute,
	Piccolo,

	Oboe,
	EnglishHorn,
	Clarinet,
	BassClarinet,

	Bassoon,
	Contrabassoon,

	SopranoSaxophone,
	AltoSaxophone,
	TenorSaxophone,
	BaritoneSaxophone,

	// ==== Brass ====
	Trumpet,
	Cornet,

	FrenchHorn,

	Trombone,
	BassTrombone,

	Euphonium,
	Tuba,

	// ==== Percussion ====
	SnareDrum,
	BassDrum,
	Cymbals,
	Triangle,
	Tambourine,

	Timpani,

	Xylophone,
	Marimba,
	Glockenspiel,
	Vibraphone,

	// ==== Keyboards ====
	#[default]
	Piano,
	Harpsichord,
	Organ,
	Celesta,
	Accordion,
	Synthesizer,
}