use std::fmt;
use std::ops::{Add, Div, Mul, Sub};
use std::str::FromStr;
use num::{One, Zero};
use super::{Fraction, ParseError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DurationType {
DuplexMaxima,
Maxima,
Longa,
Breve,
Whole,
Half,
Quarter,
Eighth,
N16th,
N32nd,
N64th,
N128th,
N256th,
N512th,
N1024th,
N2048th,
Zero,
}
const ALL_DURATION_TYPES: [DurationType; 16] = [
DurationType::DuplexMaxima,
DurationType::Maxima,
DurationType::Longa,
DurationType::Breve,
DurationType::Whole,
DurationType::Half,
DurationType::Quarter,
DurationType::Eighth,
DurationType::N16th,
DurationType::N32nd,
DurationType::N64th,
DurationType::N128th,
DurationType::N256th,
DurationType::N512th,
DurationType::N1024th,
DurationType::N2048th,
];
impl DurationType {
pub fn quarter_length(&self) -> Fraction {
match self {
DurationType::DuplexMaxima => Fraction::new(64, 1),
DurationType::Maxima => Fraction::new(32, 1),
DurationType::Longa => Fraction::new(16, 1),
DurationType::Breve => Fraction::new(8, 1),
DurationType::Whole => Fraction::new(4, 1),
DurationType::Half => Fraction::new(2, 1),
DurationType::Quarter => Fraction::one(),
DurationType::Eighth => Fraction::new(1, 2),
DurationType::N16th => Fraction::new(1, 4),
DurationType::N32nd => Fraction::new(1, 8),
DurationType::N64th => Fraction::new(1, 16),
DurationType::N128th => Fraction::new(1, 32),
DurationType::N256th => Fraction::new(1, 64),
DurationType::N512th => Fraction::new(1, 128),
DurationType::N1024th => Fraction::new(1, 256),
DurationType::N2048th => Fraction::new(1, 512),
DurationType::Zero => Fraction::zero(),
}
}
pub fn from_quarter_length(ql: Fraction) -> Option<DurationType> {
if ql == Fraction::zero() {
return Some(DurationType::Zero);
}
for t in ALL_DURATION_TYPES {
if t.quarter_length() == ql {
return Some(t);
}
}
None
}
pub fn name(&self) -> &'static str {
match self {
DurationType::DuplexMaxima => "duplex maxima",
DurationType::Maxima => "maxima",
DurationType::Longa => "longa",
DurationType::Breve => "breve",
DurationType::Whole => "whole",
DurationType::Half => "half",
DurationType::Quarter => "quarter",
DurationType::Eighth => "eighth",
DurationType::N16th => "16th",
DurationType::N32nd => "32nd",
DurationType::N64th => "64th",
DurationType::N128th => "128th",
DurationType::N256th => "256th",
DurationType::N512th => "512th",
DurationType::N1024th => "1024th",
DurationType::N2048th => "2048th",
DurationType::Zero => "zero",
}
}
pub fn from_str(s: &str) -> Result<DurationType, ParseError> {
match s.to_lowercase().as_str() {
"duplex maxima" | "duplex-maxima" => Ok(DurationType::DuplexMaxima),
"maxima" => Ok(DurationType::Maxima),
"longa" | "long" => Ok(DurationType::Longa),
"breve" | "double whole" | "double-whole" => Ok(DurationType::Breve),
"whole" | "1" | "semibreve" => Ok(DurationType::Whole),
"half" | "2" | "minim" => Ok(DurationType::Half),
"quarter" | "4" | "crotchet" => Ok(DurationType::Quarter),
"eighth" | "8" | "8th" | "quaver" => Ok(DurationType::Eighth),
"16th" | "16" | "semiquaver" => Ok(DurationType::N16th),
"32nd" | "32" | "demisemiquaver" => Ok(DurationType::N32nd),
"64th" | "64" | "hemidemisemiquaver" => Ok(DurationType::N64th),
"128th" | "128" => Ok(DurationType::N128th),
"256th" | "256" => Ok(DurationType::N256th),
"512th" | "512" => Ok(DurationType::N512th),
"1024th" | "1024" => Ok(DurationType::N1024th),
"2048th" | "2048" => Ok(DurationType::N2048th),
"zero" | "0" | "grace" => Ok(DurationType::Zero),
_ => Err(ParseError::InvalidDurationType(s.to_string())),
}
}
}
impl fmt::Display for DurationType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Tuplet {
pub actual: u8,
pub normal: u8,
pub duration_type: Option<DurationType>,
}
impl Tuplet {
pub fn new(actual: u8, normal: u8) -> Self {
Self {
actual,
normal,
duration_type: None,
}
}
pub fn triplet() -> Self {
Self::new(3, 2)
}
pub fn duplet() -> Self {
Self::new(2, 3)
}
pub fn quintuplet() -> Self {
Self::new(5, 4)
}
pub fn multiplier(&self) -> Fraction {
Fraction::new(self.normal as i64, self.actual as i64)
}
pub fn set_duration_type(&mut self, type_: DurationType) {
self.duration_type = Some(type_);
}
pub fn set_ratio(&mut self, actual: u8, normal: u8) {
self.actual = actual;
self.normal = normal;
}
pub fn total_tuplet_length(&self) -> Fraction {
let unit = self
.duration_type
.unwrap_or(DurationType::Quarter)
.quarter_length();
unit * Fraction::new(self.normal as i64, 1)
}
pub fn duration_actual(&self) -> Duration {
Duration::from_type(self.duration_type.unwrap_or(DurationType::Quarter), 0)
}
pub fn duration_normal(&self) -> Duration {
self.duration_actual()
}
}
impl Default for Tuplet {
fn default() -> Self {
Self::triplet()
}
}
pub struct TupletFixer;
impl TupletFixer {
pub fn fix(durations: &mut [Duration], tuplet: &Tuplet) -> bool {
if durations.is_empty() {
return false;
}
let target = tuplet.total_tuplet_length();
let sum: Fraction = durations.iter().map(|d| d.quarter_length()).sum();
if sum == target {
return false;
}
let diff = target - sum;
let last = durations.len() - 1;
let corrected = durations[last].quarter_length() + diff;
durations[last] = Duration::from_quarter_length(corrected);
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DurationTuple {
pub type_: DurationType,
pub dots: u8,
}
impl DurationTuple {
pub fn new(type_: DurationType, dots: u8) -> Self {
Self { type_, dots }
}
pub fn quarter_length(&self) -> Fraction {
Duration::calculate_quarter_length(self.type_, self.dots, &[])
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Duration {
quarter_length: Fraction,
type_: Option<DurationType>,
dots: u8,
tuplets: Vec<Tuplet>,
linked: bool,
}
impl Duration {
pub fn from_quarter_length(ql: impl Into<Fraction>) -> Self {
let ql = ql.into();
let (type_, dots) = Self::infer_type_and_dots(ql);
Self {
quarter_length: ql,
type_,
dots,
tuplets: Vec::new(),
linked: true,
}
}
pub fn from_type(type_: DurationType, dots: u8) -> Self {
let quarter_length = Self::calculate_quarter_length(type_, dots, &[]);
Self {
quarter_length,
type_: Some(type_),
dots,
tuplets: Vec::new(),
linked: true,
}
}
pub fn zero() -> Self {
Self::from_type(DurationType::Zero, 0)
}
pub fn whole() -> Self {
Self::from_type(DurationType::Whole, 0)
}
pub fn half() -> Self {
Self::from_type(DurationType::Half, 0)
}
pub fn quarter() -> Self {
Self::from_type(DurationType::Quarter, 0)
}
pub fn eighth() -> Self {
Self::from_type(DurationType::Eighth, 0)
}
pub fn sixteenth() -> Self {
Self::from_type(DurationType::N16th, 0)
}
pub fn quarter_length(&self) -> Fraction {
self.quarter_length
}
pub fn quarter_length_f64(&self) -> f64 {
*self.quarter_length.numer() as f64 / *self.quarter_length.denom() as f64
}
pub fn set_quarter_length(&mut self, ql: impl Into<Fraction>) {
self.quarter_length = ql.into();
if self.linked {
let (type_, dots) = Self::infer_type_and_dots(self.quarter_length);
self.type_ = type_;
self.dots = dots;
}
}
pub fn type_(&self) -> Option<DurationType> {
self.type_
}
pub fn set_type(&mut self, type_: DurationType) {
self.type_ = Some(type_);
if self.linked {
self.quarter_length = Self::calculate_quarter_length(type_, self.dots, &self.tuplets);
}
}
pub fn dots(&self) -> u8 {
self.dots
}
pub fn set_dots(&mut self, dots: u8) {
self.dots = dots;
if self.linked {
if let Some(type_) = self.type_ {
self.quarter_length = Self::calculate_quarter_length(type_, dots, &self.tuplets);
}
}
}
pub fn tuplets(&self) -> &[Tuplet] {
&self.tuplets
}
pub fn add_tuplet(&mut self, tuplet: Tuplet) {
self.tuplets.push(tuplet);
if self.linked {
if let Some(type_) = self.type_ {
self.quarter_length =
Self::calculate_quarter_length(type_, self.dots, &self.tuplets);
}
}
}
pub fn clear_tuplets(&mut self) {
self.tuplets.clear();
if self.linked {
if let Some(type_) = self.type_ {
self.quarter_length =
Self::calculate_quarter_length(type_, self.dots, &self.tuplets);
}
}
}
pub fn linked(&self) -> bool {
self.linked
}
pub fn set_linked(&mut self, linked: bool) {
self.linked = linked;
}
pub fn augment_or_diminish(&self, scalar: impl Into<Fraction>) -> Duration {
let new_ql = self.quarter_length * scalar.into();
Duration::from_quarter_length(new_ql)
}
pub fn augment(&self) -> Duration {
self.augment_or_diminish(Fraction::new(2, 1))
}
pub fn diminish(&self) -> Duration {
self.augment_or_diminish(Fraction::new(1, 2))
}
pub fn is_complex(&self) -> bool {
self.type_.is_none()
}
pub fn components(&self) -> Vec<DurationTuple> {
if let Some(type_) = self.type_ {
return vec![DurationTuple::new(type_, self.dots)];
}
let mut remaining = self.quarter_length_no_tuplets();
let mut components = Vec::new();
let mut guard = 0;
while remaining > Fraction::zero() && guard < 16 {
guard += 1;
let mut best: Option<(DurationType, u8, Fraction)> = None;
for type_ in ALL_DURATION_TYPES {
for dots in 0..=4u8 {
let ql = Self::calculate_quarter_length(type_, dots, &[]);
if ql <= remaining && best.is_none_or(|(_, _, best_ql)| ql > best_ql) {
best = Some((type_, dots, ql));
}
}
}
match best {
Some((type_, dots, ql)) if ql > Fraction::zero() => {
components.push(DurationTuple::new(type_, dots));
remaining = remaining - ql;
}
_ => break,
}
}
components
}
pub fn add_duration_tuple(&mut self, type_: DurationType, dots: u8) {
let added = Self::calculate_quarter_length(type_, dots, &[]);
self.set_quarter_length(self.quarter_length + added);
}
pub fn consolidate(durations: &[Duration]) -> Duration {
let total: Fraction = durations.iter().map(|d| d.quarter_length).sum();
Duration::from_quarter_length(total)
}
pub fn split_dot_groups(&self) -> Vec<Duration> {
match self.type_ {
Some(type_) if self.dots > 0 => {
let mut pieces = vec![Duration::from_type(type_, 0)];
let mut piece_len = type_.quarter_length() / 2;
for _ in 0..self.dots {
pieces.push(Duration::from_quarter_length(piece_len));
piece_len = piece_len / 2;
}
pieces
}
_ => vec![self.clone()],
}
}
pub fn slice_component_at_position(&self, position: Fraction) -> Option<DurationTuple> {
let index = self.component_index_at_qtr_position(position)?;
self.components().get(index).copied()
}
pub fn component_index_at_qtr_position(&self, position: Fraction) -> Option<usize> {
if position < Fraction::zero() || position >= self.quarter_length {
return None;
}
let mut offset = Fraction::zero();
for (i, c) in self.components().iter().enumerate() {
let len = c.quarter_length();
if position < offset + len {
return Some(i);
}
offset = offset + len;
}
None
}
pub fn component_start_time(&self, index: usize) -> Option<Fraction> {
let comps = self.components();
if index >= comps.len() {
return None;
}
let mut offset = Fraction::zero();
for c in &comps[..index] {
offset = offset + c.quarter_length();
}
Some(offset)
}
pub fn quarter_length_no_tuplets(&self) -> Fraction {
let mut ql = self.quarter_length;
for tuplet in &self.tuplets {
ql = ql / tuplet.multiplier();
}
ql
}
pub fn aggregate_tuplet_multiplier(&self) -> Fraction {
self.tuplets
.iter()
.fold(Fraction::one(), |acc, t| acc * t.multiplier())
}
pub fn full_name(&self) -> String {
let mut name = String::new();
if let Some(type_) = self.type_ {
for _ in 0..self.dots {
name.push_str("Dotted ");
}
name.push_str(type_.name());
for tuplet in &self.tuplets {
name.push_str(&format!(" ({} in {})", tuplet.actual, tuplet.normal));
}
} else {
name = format!("Complex ({})", self.quarter_length);
}
name
}
fn calculate_quarter_length(type_: DurationType, dots: u8, tuplets: &[Tuplet]) -> Fraction {
let base = type_.quarter_length();
let mut ql = base;
let mut dot_value = base / 2;
for _ in 0..dots {
ql = ql + dot_value;
dot_value = dot_value / 2;
}
for tuplet in tuplets {
ql = ql * tuplet.multiplier();
}
ql
}
fn infer_type_and_dots(ql: Fraction) -> (Option<DurationType>, u8) {
if ql == Fraction::zero() {
return (Some(DurationType::Zero), 0);
}
for type_ in ALL_DURATION_TYPES {
for dots in 0..=4 {
if Self::calculate_quarter_length(type_, dots, &[]) == ql {
return (Some(type_), dots);
}
}
}
(None, 0)
}
}
impl Default for Duration {
fn default() -> Self {
Self::quarter()
}
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.full_name())
}
}
impl Add for Duration {
type Output = Duration;
fn add(self, rhs: Self) -> Self::Output {
Duration::from_quarter_length(self.quarter_length + rhs.quarter_length)
}
}
impl Sub for Duration {
type Output = Duration;
fn sub(self, rhs: Self) -> Self::Output {
Duration::from_quarter_length(self.quarter_length - rhs.quarter_length)
}
}
impl Mul<Fraction> for Duration {
type Output = Duration;
fn mul(self, rhs: Fraction) -> Self::Output {
Duration::from_quarter_length(self.quarter_length * rhs)
}
}
impl Div<Fraction> for Duration {
type Output = Duration;
fn div(self, rhs: Fraction) -> Self::Output {
Duration::from_quarter_length(self.quarter_length / rhs)
}
}
impl From<DurationType> for Duration {
fn from(type_: DurationType) -> Self {
Duration::from_type(type_, 0)
}
}
impl FromStr for Duration {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(type_) = DurationType::from_str(s) {
return Ok(Duration::from_type(type_, 0));
}
if let Some((num, denom)) = s.split_once('/') {
if let (Ok(n), Ok(d)) = (num.trim().parse::<i64>(), denom.trim().parse::<i64>()) {
return Ok(Duration::from_quarter_length(Fraction::new(n, d)));
}
}
if let Ok(f) = s.parse::<f64>() {
let denom = 256i64;
let numer = (f * denom as f64).round() as i64;
return Ok(Duration::from_quarter_length(Fraction::new(numer, denom)));
}
Err(ParseError::InvalidDurationType(s.to_string()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GraceDuration {
pub notated_type: DurationType,
pub notated_dots: u8,
pub slash: bool,
}
impl GraceDuration {
pub fn new(notated_type: DurationType, notated_dots: u8) -> Self {
Self {
notated_type,
notated_dots,
slash: true,
}
}
pub fn appoggiatura(notated_type: DurationType, notated_dots: u8) -> Self {
Self {
notated_type,
notated_dots,
slash: false,
}
}
pub fn notated_quarter_length(&self) -> Fraction {
Duration::calculate_quarter_length(self.notated_type, self.notated_dots, &[])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_duration_type_variants() {
assert_eq!(
DurationType::DuplexMaxima.quarter_length(),
Fraction::new(64, 1)
);
assert_eq!(DurationType::N512th.quarter_length(), Fraction::new(1, 128));
assert_eq!(DurationType::N1024th.quarter_length(), Fraction::new(1, 256));
assert_eq!(DurationType::N2048th.quarter_length(), Fraction::new(1, 512));
assert_eq!(
DurationType::from_str("512th").unwrap(),
DurationType::N512th
);
}
#[test]
fn test_components_simple_duration_is_single_component() {
let d = Duration::quarter();
let comps = d.components();
assert_eq!(comps.len(), 1);
assert_eq!(comps[0], DurationTuple::new(DurationType::Quarter, 0));
}
#[test]
fn test_components_decompose_complex_duration() {
let d = Duration::from_quarter_length(Fraction::new(5, 4));
assert!(d.is_complex());
let comps = d.components();
assert!(comps.len() > 1, "expected a genuine tied decomposition");
let total: Fraction = comps.iter().map(|c| c.quarter_length()).sum();
assert_eq!(total, Fraction::new(5, 4));
assert!(comps[0].quarter_length() >= comps.last().unwrap().quarter_length());
}
#[test]
fn test_component_index_and_start_time() {
let d = Duration::from_quarter_length(Fraction::new(5, 4));
let comps = d.components();
assert_eq!(d.component_index_at_qtr_position(Fraction::zero()), Some(0));
assert_eq!(d.component_start_time(0), Some(Fraction::zero()));
let last_index = comps.len() - 1;
let last_start = d.component_start_time(last_index).unwrap();
assert_eq!(last_start + comps[last_index].quarter_length(), Fraction::new(5, 4));
assert_eq!(d.component_index_at_qtr_position(Fraction::new(5, 4)), None);
assert_eq!(d.component_start_time(comps.len()), None);
}
#[test]
fn test_add_duration_tuple_and_consolidate() {
let mut d = Duration::quarter();
d.add_duration_tuple(DurationType::Eighth, 0);
assert_eq!(d.quarter_length(), Fraction::new(3, 2));
assert_eq!(d.type_(), Some(DurationType::Quarter)); assert_eq!(d.dots(), 1);
let consolidated = Duration::consolidate(&[Duration::quarter(), Duration::eighth()]);
assert_eq!(consolidated.quarter_length(), Fraction::new(3, 2));
}
#[test]
fn test_split_dot_groups() {
let dotted_quarter = Duration::from_type(DurationType::Quarter, 1);
let pieces = dotted_quarter.split_dot_groups();
assert_eq!(pieces.len(), 2);
assert_eq!(pieces[0].quarter_length(), Fraction::one());
assert_eq!(pieces[1].quarter_length(), Fraction::new(1, 2));
let plain_quarter = Duration::quarter();
assert_eq!(plain_quarter.split_dot_groups().len(), 1);
}
#[test]
fn test_quarter_length_no_tuplets_and_aggregate_multiplier() {
let mut d = Duration::quarter();
d.add_tuplet(Tuplet::triplet());
assert_eq!(d.quarter_length(), Fraction::new(2, 3));
assert_eq!(d.quarter_length_no_tuplets(), Fraction::one());
assert_eq!(d.aggregate_tuplet_multiplier(), Fraction::new(2, 3));
}
#[test]
fn test_tuplet_additions() {
let mut t = Tuplet::triplet();
t.set_duration_type(DurationType::Eighth);
assert_eq!(t.total_tuplet_length(), Fraction::new(1, 1)); assert_eq!(t.duration_actual().quarter_length(), Fraction::new(1, 2));
assert_eq!(t.duration_normal().quarter_length(), Fraction::new(1, 2));
t.set_ratio(5, 4);
assert_eq!(t.actual, 5);
assert_eq!(t.normal, 4);
}
#[test]
fn test_tuplet_fixer() {
let mut tuplet = Tuplet::triplet();
tuplet.set_duration_type(DurationType::Eighth);
let mut durations = vec![
Duration::from_quarter_length(Fraction::new(1, 3)),
Duration::from_quarter_length(Fraction::new(1, 3)),
Duration::from_quarter_length(Fraction::new(1, 4)), ];
let fixed = TupletFixer::fix(&mut durations, &tuplet);
assert!(fixed);
let total: Fraction = durations.iter().map(|d| d.quarter_length()).sum();
assert_eq!(total, Fraction::one());
let mut correct = vec![Duration::from_quarter_length(Fraction::one())];
assert!(!TupletFixer::fix(&mut correct, &Tuplet::new(1, 1)));
}
#[test]
fn test_grace_duration() {
let grace = GraceDuration::new(DurationType::Eighth, 0);
assert!(grace.slash);
assert_eq!(grace.notated_quarter_length(), Fraction::new(1, 2));
let appoggiatura = GraceDuration::appoggiatura(DurationType::Eighth, 0);
assert!(!appoggiatura.slash);
}
#[test]
fn test_duration_quarter_length() {
assert_eq!(Duration::whole().quarter_length(), Fraction::new(4, 1));
assert_eq!(Duration::half().quarter_length(), Fraction::new(2, 1));
assert_eq!(Duration::quarter().quarter_length(), Fraction::one());
assert_eq!(Duration::eighth().quarter_length(), Fraction::new(1, 2));
}
#[test]
fn test_duration_from_quarter_length() {
let d = Duration::from_quarter_length(Fraction::new(4, 1));
assert_eq!(d.type_(), Some(DurationType::Whole));
assert_eq!(d.dots(), 0);
let d = Duration::from_quarter_length(Fraction::new(3, 2));
assert_eq!(d.type_(), Some(DurationType::Quarter));
assert_eq!(d.dots(), 1); }
#[test]
fn test_duration_dots() {
let dotted_half = Duration::from_type(DurationType::Half, 1);
assert_eq!(dotted_half.quarter_length(), Fraction::new(3, 1));
let double_dotted_quarter = Duration::from_type(DurationType::Quarter, 2);
assert_eq!(double_dotted_quarter.quarter_length(), Fraction::new(7, 4));
}
#[test]
fn test_duration_tuplet() {
let mut d = Duration::quarter();
d.add_tuplet(Tuplet::triplet());
assert_eq!(d.quarter_length(), Fraction::new(2, 3));
}
#[test]
fn test_duration_arithmetic() {
let half = Duration::half();
let quarter = Duration::quarter();
let sum = half.clone() + quarter.clone();
assert_eq!(sum.quarter_length(), Fraction::new(3, 1));
let diff = half - quarter;
assert_eq!(diff.quarter_length(), Fraction::one());
}
#[test]
fn test_duration_augment_diminish() {
let quarter = Duration::quarter();
let half = quarter.augment();
assert_eq!(half.quarter_length(), Fraction::new(2, 1));
let eighth = quarter.diminish();
assert_eq!(eighth.quarter_length(), Fraction::new(1, 2));
}
}