Skip to content

Commit

Permalink
Performance: allow to fallback to default Player
Browse files Browse the repository at this point in the history
  • Loading branch information
tsionyx committed Feb 9, 2024
1 parent 183a467 commit 1c2d458
Showing 1 changed file with 65 additions and 14 deletions.
79 changes: 65 additions & 14 deletions src/music/performance.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -18,7 +18,10 @@ pub struct Performance {
repr: Vec<Event>,
}

impl<P> Music<P> {
impl<P> Music<P>
where
Player<P>: Default,
{
pub fn perform<'p>(&self, players: &'p PlayerMap<P>, ctx: Context<'p, P>) -> Performance {
self.perf(players, ctx).0
}
Expand All @@ -31,7 +34,7 @@ impl<P> Music<P> {
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![] },
Expand Down Expand Up @@ -72,11 +75,12 @@ impl<P> Music<P> {
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)
}
Expand Down Expand Up @@ -117,7 +121,7 @@ pub type Duration = Ratio<u32>;
/// as we go through the interpretation.
pub struct Context<'p, P> {
start_time: TimePoint,
player: &'p Player<P>,
player: Cow<'p, Player<P>>,
instrument: InstrumentName,
whole_note: Duration,
pitch: AbsPitch,
Expand All @@ -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,
Expand Down Expand Up @@ -170,14 +174,25 @@ pub struct Player<P> {
notate_player: NotateFun<P>,
}

impl<P> Clone for Player<P> {
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<P> fmt::Debug for Player<P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Player {}", self.name)
}
}

type NoteFun<P> = Box<dyn Fn(Context<P>, Dur, &P) -> Performance>;
type PhraseFun<P> = Box<
type NoteFun<P> = Arc<dyn Fn(Context<P>, Dur, &P) -> Performance>;
type PhraseFun<P> = Arc<
dyn Fn(&Music<P>, &PlayerMap<P>, Context<P>, &[PhraseAttribute]) -> (Performance, Duration),
>;
// TODO: producing a properly notated score is not defined yet
Expand All @@ -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,
Expand Down Expand Up @@ -255,7 +270,7 @@ mod defaults {
Player<P>: 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)
Expand Down Expand Up @@ -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<P>) -> Self {
pub fn with_player(player: Cow<'p, Player<P>>) -> Self {
Self {
start_time: TimePoint::from_integer(0),
player,
Expand All @@ -484,4 +499,40 @@ mod defaults {
}
}
}

impl<P> Default for Context<'_, P>
where
P: 'static,
Player<P>: 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());
}
}

0 comments on commit 1c2d458

Please sign in to comment.