use crate::core::{Accidental, Duration, Fraction, Interval, Note, Pitch};
use crate::notation::{Key, Scale, Spanner, SpannerAnchor};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum OrnamentSize {
#[default]
Diatonic,
Half,
Whole,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OrnamentKind {
Trill,
Turn,
InvertedTurn,
Mordent,
InvertedMordent,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Ornament {
kind: OrnamentKind,
size: OrnamentSize,
accidental: Option<Accidental>,
}
impl Ornament {
pub fn new(kind: OrnamentKind) -> Self {
Self {
kind,
size: OrnamentSize::Diatonic,
accidental: None,
}
}
pub fn trill() -> Self {
Self::new(OrnamentKind::Trill)
}
pub fn turn() -> Self {
Self::new(OrnamentKind::Turn)
}
pub fn inverted_turn() -> Self {
Self::new(OrnamentKind::InvertedTurn)
}
pub fn mordent() -> Self {
Self::new(OrnamentKind::Mordent)
}
pub fn inverted_mordent() -> Self {
Self::new(OrnamentKind::InvertedMordent)
}
pub fn kind(&self) -> OrnamentKind {
self.kind
}
pub fn with_size(mut self, size: OrnamentSize) -> Self {
self.size = size;
self
}
pub fn size(&self) -> OrnamentSize {
self.size
}
pub fn with_accidental(mut self, accidental: Accidental) -> Self {
self.accidental = Some(accidental);
self
}
pub fn accidental(&self) -> Option<Accidental> {
self.accidental
}
pub fn resolve_ornamental_pitches(&self, main_pitch: &Pitch, key: &Key) -> Vec<Pitch> {
let upper = self.neighbor_pitch(main_pitch, key, true);
let lower = self.neighbor_pitch(main_pitch, key, false);
match self.kind {
OrnamentKind::Trill => vec![main_pitch.clone(), upper],
OrnamentKind::Mordent => vec![main_pitch.clone(), lower, main_pitch.clone()],
OrnamentKind::InvertedMordent => vec![main_pitch.clone(), upper, main_pitch.clone()],
OrnamentKind::Turn => vec![upper, main_pitch.clone(), lower, main_pitch.clone()],
OrnamentKind::InvertedTurn => vec![lower, main_pitch.clone(), upper, main_pitch.clone()],
}
}
pub fn realize(&self, main_note: &Note, key: &Key) -> Vec<Note> {
let pattern = self.resolve_ornamental_pitches(main_note.pitch(), key);
let total = main_note.duration().quarter_length();
let note_count = match self.kind {
OrnamentKind::Trill => {
let sixteenth = Fraction::new(1, 4);
let subdivisions = (total / sixteenth).to_integer().max(2);
(subdivisions as usize).clamp(2, 32)
}
_ => pattern.len(),
};
let each_length = total / Fraction::from(note_count as i64);
let each_duration = Duration::from_quarter_length(each_length);
(0..note_count)
.map(|i| Note::new(pattern[i % pattern.len()].clone(), each_duration.clone()))
.collect()
}
fn neighbor_pitch(&self, main_pitch: &Pitch, key: &Key, upper: bool) -> Pitch {
match self.size {
OrnamentSize::Half => main_pitch.transpose_semitones(if upper { 1 } else { -1 }),
OrnamentSize::Whole => main_pitch.transpose_semitones(if upper { 2 } else { -2 }),
OrnamentSize::Diatonic => {
let mut candidate = diatonic_neighbor(main_pitch, key, upper);
if let Some(accidental) = self.accidental {
candidate.set_accidental(Some(accidental));
}
candidate
}
}
}
}
fn diatonic_neighbor(main_pitch: &Pitch, key: &Key, upper: bool) -> Pitch {
let scale = Scale::new(key.tonic().clone(), key.mode());
let Some(degree) = scale.degree_of(main_pitch) else {
return main_pitch.transpose_semitones(if upper { 1 } else { -1 });
};
let neighbor_degree = if upper {
if degree == 7 { 1 } else { degree + 1 }
} else if degree == 1 {
7
} else {
degree - 1
};
let mut candidate = scale
.pitch_for_degree(neighbor_degree)
.expect("degree is always in 1..=7");
candidate.set_octave(Some(main_pitch.implicit_octave()));
if upper && candidate.midi() <= main_pitch.midi() {
candidate = candidate.transpose(&Interval::octave());
} else if !upper && candidate.midi() >= main_pitch.midi() {
candidate = candidate.transpose(&Interval::octave().reverse());
}
candidate
}
#[derive(Debug, Clone, PartialEq)]
pub struct RehearsalMark {
text: String,
}
impl RehearsalMark {
pub fn new(text: impl Into<String>) -> Self {
Self { text: text.into() }
}
pub fn text(&self) -> &str {
&self.text
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TextExpression {
text: String,
}
impl TextExpression {
pub fn new(text: impl Into<String>) -> Self {
Self { text: text.into() }
}
pub fn text(&self) -> &str {
&self.text
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PedalMark {
spanner: Spanner,
}
impl PedalMark {
pub fn new(start: SpannerAnchor, end: SpannerAnchor) -> Self {
Self {
spanner: Spanner::with_label(start, end, "pedal"),
}
}
pub fn spanner(&self) -> &Spanner {
&self.spanner
}
pub fn contains(&self, pos: SpannerAnchor) -> bool {
self.spanner.contains(pos)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArpeggioDirection {
Up,
Down,
NonArpeggio,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArpeggioMark {
spanner: Spanner,
direction: ArpeggioDirection,
}
impl ArpeggioMark {
pub fn new(direction: ArpeggioDirection, start: SpannerAnchor, end: SpannerAnchor) -> Self {
Self {
spanner: Spanner::with_label(start, end, "arpeggio"),
direction,
}
}
pub fn direction(&self) -> ArpeggioDirection {
self.direction
}
pub fn spanner(&self) -> &Spanner {
&self.spanner
}
pub fn contains(&self, pos: SpannerAnchor) -> bool {
self.spanner.contains(pos)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TrillExtension {
spanner: Spanner,
}
impl TrillExtension {
pub fn new(start: SpannerAnchor, end: SpannerAnchor) -> Self {
Self {
spanner: Spanner::with_label(start, end, "trill extension"),
}
}
pub fn spanner(&self) -> &Spanner {
&self.spanner
}
pub fn contains(&self, pos: SpannerAnchor) -> bool {
self.spanner.contains(pos)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct TremoloSpanner {
spanner: Spanner,
strokes: u8,
}
impl TremoloSpanner {
pub fn new(start: SpannerAnchor, end: SpannerAnchor, strokes: u8) -> Self {
Self {
spanner: Spanner::with_label(start, end, "tremolo"),
strokes,
}
}
pub fn strokes(&self) -> u8 {
self.strokes
}
pub fn spanner(&self) -> &Spanner {
&self.spanner
}
pub fn contains(&self, pos: SpannerAnchor) -> bool {
self.spanner.contains(pos)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum TurnDelay {
#[default]
OnBeat,
Delayed,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Step;
use crate::notation::KeyMode;
fn c_major_key() -> Key {
Key::new(Pitch::from_parts(Step::C, Some(4), None), KeyMode::Major)
}
#[test]
fn test_mordent_resolves_lower_neighbor_diatonically() {
let key = c_major_key();
let main = Pitch::from_parts(Step::C, Some(5), None);
let pitches = Ornament::mordent().resolve_ornamental_pitches(&main, &key);
assert_eq!(pitches.len(), 3);
assert_eq!(pitches[0].name(), "C");
assert_eq!(pitches[1].name(), "B"); assert_eq!(pitches[1].octave(), Some(4)); assert_eq!(pitches[2].name(), "C");
}
#[test]
fn test_inverted_mordent_resolves_upper_neighbor() {
let key = c_major_key();
let main = Pitch::from_parts(Step::C, Some(4), None);
let pitches = Ornament::inverted_mordent().resolve_ornamental_pitches(&main, &key);
assert_eq!(pitches[1].name(), "D");
assert_eq!(pitches[1].octave(), Some(4));
}
#[test]
fn test_turn_pattern_order() {
let key = c_major_key();
let main = Pitch::from_parts(Step::E, Some(4), None);
let pitches = Ornament::turn().resolve_ornamental_pitches(&main, &key);
let names: Vec<String> = pitches.iter().map(|p| p.name()).collect();
assert_eq!(names, vec!["F", "E", "D", "E"]);
}
#[test]
fn test_trill_realize_fills_duration_alternating_main_and_upper() {
let key = c_major_key();
let pitch = Pitch::from_parts(Step::C, Some(4), None);
let note = Note::new(pitch, Duration::quarter());
let realized = Ornament::trill().realize(¬e, &key);
assert!(realized.len() >= 2);
let total: Fraction = realized.iter().map(|n| n.duration().quarter_length()).sum();
assert_eq!(total, Fraction::new(1, 1));
for (i, n) in realized.iter().enumerate() {
let expected = if i % 2 == 0 { "C" } else { "D" };
assert_eq!(n.pitch().name(), expected);
}
}
#[test]
fn test_ornament_size_override_forces_chromatic_step() {
let key = c_major_key();
let main = Pitch::from_parts(Step::C, Some(4), None);
let half_step_mordent = Ornament::mordent().with_size(OrnamentSize::Half);
let pitches = half_step_mordent.resolve_ornamental_pitches(&main, &key);
assert_eq!(main.midi() - pitches[1].midi(), 1);
}
#[test]
fn test_non_scale_tone_falls_back_to_chromatic_step() {
let key = c_major_key();
let main = Pitch::from_parts(Step::F, Some(4), Some(Accidental::Sharp));
let pitches = Ornament::mordent().resolve_ornamental_pitches(&main, &key);
assert_eq!(main.midi() - pitches[1].midi(), 1);
}
#[test]
fn test_rehearsal_mark_and_text_expression() {
let mark = RehearsalMark::new("A");
assert_eq!(mark.text(), "A");
let text = TextExpression::new("dolce");
assert_eq!(text.text(), "dolce");
}
#[test]
fn test_pedal_arpeggio_trill_extension_and_tremolo_spanners() {
let start = SpannerAnchor::new(1, Fraction::new(0, 1));
let end = SpannerAnchor::new(1, Fraction::new(4, 1));
let pedal = PedalMark::new(start, end);
assert!(pedal.contains(SpannerAnchor::new(1, Fraction::new(2, 1))));
let arpeggio = ArpeggioMark::new(ArpeggioDirection::Up, start, end);
assert_eq!(arpeggio.direction(), ArpeggioDirection::Up);
let trill_ext = TrillExtension::new(start, end);
assert!(trill_ext.contains(start));
let tremolo = TremoloSpanner::new(start, end, 3);
assert_eq!(tremolo.strokes(), 3);
}
}