use crate::notation::{Spanner, SpannerAnchor};
pub use crate::core::ArticulationMark;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ArticulationPlacement {
#[default]
Auto,
Above,
Below,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HammerPullType {
HammerOn,
PullOff,
}
impl HammerPullType {
pub fn text(&self) -> &'static str {
match self {
HammerPullType::HammerOn => "H",
HammerPullType::PullOff => "P",
}
}
}
impl std::fmt::Display for HammerPullType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.text())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HammerPullSpanner {
spanner: Spanner,
kind: HammerPullType,
}
impl HammerPullSpanner {
pub fn hammer_on(start: SpannerAnchor, end: SpannerAnchor) -> Self {
Self {
spanner: Spanner::with_label(start, end, HammerPullType::HammerOn.to_string()),
kind: HammerPullType::HammerOn,
}
}
pub fn pull_off(start: SpannerAnchor, end: SpannerAnchor) -> Self {
Self {
spanner: Spanner::with_label(start, end, HammerPullType::PullOff.to_string()),
kind: HammerPullType::PullOff,
}
}
pub fn kind(&self) -> HammerPullType {
self.kind
}
pub fn spanner(&self) -> &Spanner {
&self.spanner
}
pub fn contains(&self, pos: SpannerAnchor) -> bool {
self.spanner.contains(pos)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Fraction;
#[test]
fn test_articulation_mark() {
let staccato = ArticulationMark::Staccato;
assert_eq!(staccato.name(), "staccato");
assert!(staccato.affects_duration());
assert!(staccato.duration_multiplier() < 1.0);
}
#[test]
fn test_accent_velocity() {
let accent = ArticulationMark::Accent;
assert!(accent.velocity_multiplier() > 1.0);
}
#[test]
fn test_fermata() {
assert!(ArticulationMark::Fermata.is_fermata());
assert!(ArticulationMark::ShortFermata.is_fermata());
assert!(!ArticulationMark::Staccato.is_fermata());
}
#[test]
fn test_new_articulation_variants() {
assert_eq!(ArticulationMark::Stress.name(), "stress");
assert_eq!(ArticulationMark::Unstress.name(), "unstress");
assert_eq!(ArticulationMark::StringHarmonic.name(), "string harmonic");
assert_eq!(ArticulationMark::HandbellIndication.name(), "handbell indication");
assert_eq!(ArticulationMark::HarpFingerNails.name(), "harp fingernails");
assert_eq!(ArticulationMark::WoodwindIndication.name(), "woodwind indication");
assert_eq!(ArticulationMark::BrassIndication.name(), "brass indication");
}
#[test]
fn test_fret_and_string_number_markings() {
let fret = ArticulationMark::Fret(3);
assert_eq!(fret.fret_number(), Some(3));
assert_eq!(fret.name(), "fret");
assert_eq!(ArticulationMark::Accent.fret_number(), None);
let string_num = ArticulationMark::StringNumber(2);
assert_eq!(string_num.string_number(), Some(2));
}
#[test]
fn test_additive_volume_shift_alongside_multiplier() {
use crate::core::{Articulation, Volume};
assert_eq!(ArticulationMark::Accent.volume_shift(), 16);
assert_eq!(ArticulationMark::Staccato.volume_shift(), 0);
let accent = Articulation::new(ArticulationMark::Accent);
let quiet = Volume::from_velocity(20);
let loud = Volume::from_velocity(110);
let quiet_realized = quiet.get_realized(std::slice::from_ref(&accent));
let loud_realized = loud.get_realized(std::slice::from_ref(&accent));
assert!(quiet_realized.velocity > quiet.velocity + 10);
assert!(loud_realized.velocity <= 127);
assert!(loud_realized.velocity > loud.velocity);
}
#[test]
fn test_hammer_on_and_pull_off_spanners() {
let start = SpannerAnchor::new(1, Fraction::new(0, 1));
let end = SpannerAnchor::new(1, Fraction::new(1, 1));
let hammer_on = HammerPullSpanner::hammer_on(start, end);
assert_eq!(hammer_on.kind(), HammerPullType::HammerOn);
assert!(hammer_on.contains(start));
assert!(hammer_on.contains(end));
assert!(!hammer_on.contains(SpannerAnchor::new(2, Fraction::new(0, 1))));
let pull_off = HammerPullSpanner::pull_off(start, end);
assert_eq!(pull_off.kind(), HammerPullType::PullOff);
assert_eq!(pull_off.kind().text(), "P");
assert_eq!(hammer_on.kind().text(), "H");
}
}