diff --git a/src/music/performance.rs b/src/music/performance.rs index f878aa7..3eb9d15 100644 --- a/src/music/performance.rs +++ b/src/music/performance.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, fmt}; +use std::{borrow::Cow, collections::HashMap, fmt, sync::Arc}; use itertools::Itertools as _; use num_rational::Ratio; @@ -18,7 +18,10 @@ pub struct Performance { repr: Vec, } -impl

Music

{ +impl

Music

+where + Player

: Default, +{ pub fn perform<'p>(&self, players: &'p PlayerMap

, ctx: Context<'p, P>) -> Performance { self.perf(players, ctx).0 } @@ -31,7 +34,7 @@ impl

Music

{ match self { Self::Prim(Primitive::Note(d, p)) => { let dur = d.into_ratio() * ctx.whole_note; - ((ctx.player.play_note)(ctx, *d, p), dur) + ((ctx.player.play_note.clone())(ctx, *d, p), dur) } Self::Prim(Primitive::Rest(d)) => ( Performance { repr: vec![] }, @@ -72,11 +75,12 @@ impl

Music

{ m.perf(players, ctx) } Self::Modify(Control::Phrase(phrases), m) => { - (ctx.player.interpret_phrase)(m, players, ctx, phrases) + (ctx.player.interpret_phrase.clone())(m, players, ctx, phrases) } Self::Modify(Control::Player(p), m) => { - // TODO - let player = players.get(p).expect("not found player"); + let player = players + .get(p) + .map_or_else(|| Cow::Owned(Player::default()), Cow::Borrowed); ctx.player = player; m.perf(players, ctx) } @@ -117,7 +121,7 @@ pub type Duration = Ratio; /// as we go through the interpretation. pub struct Context<'p, P> { start_time: TimePoint, - player: &'p Player

, + player: Cow<'p, Player

>, instrument: InstrumentName, whole_note: Duration, pitch: AbsPitch, @@ -138,7 +142,7 @@ impl<'p, P> Clone for Context<'p, P> { } = self; Self { start_time: *start_time, - player: *player, + player: player.clone(), instrument: instrument.clone(), whole_note: *whole_note, pitch: *pitch, @@ -170,14 +174,25 @@ pub struct Player

{ notate_player: NotateFun

, } +impl

Clone for Player

{ + fn clone(&self) -> Self { + Self { + name: self.name.clone(), + play_note: self.play_note.clone(), + interpret_phrase: self.interpret_phrase.clone(), + notate_player: self.notate_player, + } + } +} + impl

fmt::Debug for Player

{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Player {}", self.name) } } -type NoteFun

= Box, Dur, &P) -> Performance>; -type PhraseFun

= Box< +type NoteFun

= Arc, Dur, &P) -> Performance>; +type PhraseFun

= Arc< dyn Fn(&Music

, &PlayerMap

, Context

, &[PhraseAttribute]) -> (Performance, Duration), >; // TODO: producing a properly notated score is not defined yet @@ -203,7 +218,7 @@ mod defaults { where Attr: 'static, { - Box::new(move |ctx, dur, (note_pitch, attrs)| { + Arc::new(move |ctx, dur, (note_pitch, attrs)| { let Context { start_time, player: _ignore_player, @@ -255,7 +270,7 @@ mod defaults { Player

: Default, PhraseF: Fn(Performance, &PhraseAttribute) -> Performance + 'static, { - Box::new(move |music, players, ctx, attrs| { + Arc::new(move |music, players, ctx, attrs| { let (perf, dur) = music.perf(players, ctx); let perf = attrs.iter().fold(perf, &attr_modifier); (perf, dur) @@ -465,14 +480,14 @@ mod defaults { /// with changed interpretations of the [phrases][PhraseAttribute]. pub fn fancy() -> Self { Self { - interpret_phrase: Box::new(fancy_interpret_phrase), + interpret_phrase: Arc::new(fancy_interpret_phrase), ..Self::default() } } } impl<'p, P> Context<'p, P> { - pub fn with_player(player: &'p Player

) -> Self { + pub fn with_player(player: Cow<'p, Player

>) -> Self { Self { start_time: TimePoint::from_integer(0), player, @@ -484,4 +499,40 @@ mod defaults { } } } + + impl

Default for Context<'_, P> + where + P: 'static, + Player

: Default, + { + fn default() -> Self { + Self::with_player(Cow::Owned(Player::fancy())) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn john_cage() { + // 136.5 whole notes with tempo (120 QN/min) + // will last exactly 4'33" + let m = Music::line( + [Dur::from(136), Dur::HN] + .into_iter() + .map(Music::rest) + .collect(), + ); + + let players: PlayerMap<_> = [Player::default(), Player::fancy()] + .into_iter() + .map(|p| (p.name.clone(), p)) + .collect(); + let ctx = Context::with_player(Cow::Borrowed(players.get("Default").unwrap())); + let perf = m.perform(&players, ctx); + + assert!(perf.repr.is_empty()); + } }