Skip to content

Commit

Permalink
WIP: chapter 8: exercises
Browse files Browse the repository at this point in the history
  • Loading branch information
tsionyx committed Feb 11, 2024
1 parent 02b7492 commit 4e23a5e
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 0 deletions.
177 changes: 177 additions & 0 deletions examples/hsom-exercises/ch8.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/// Exercise 8.1
///
/// Gradually scale the volume of each event in the
/// performance by a factor of 1 through 1 + x,
/// using linear interpolation.
const fn scale_volume() {
// this is already done in the definition of Fancy Player
// see the inflate in the `fn fancy_interpret_phrase`.
}

// TODO: Exercises 8.2, 8.3, 8.5 should be defined for the "Fancy" player
// 8.2: Ornament::Trill, Ornament::Mordent, Ornament::InvMordent, Ornament::Turn;
// 8.3: Articulation::Pedal, Ornament::ArpeggioUp, Ornament::ArpeggioDown;
// 8.5: Ornament::DiatonicTrans;

/// Exercise 8.4
///
/// Define a player `jazzMan` that plays a melody using a jazz “swing” feel.
/// Since there are different kinds and degrees of swing, we can be more specific
/// as follows: whenever there is a sequence of two eighth notes,
/// they should be interpreted instead as a quarter note followed by an eighth note,
/// but with tempo 3/2. So in essence, the first note is lengthened,
/// and the second note is shortened, so that the first note is twice as long as the second,
/// but they still take up the same amount of overall time.
///
/// (Hint: There are several ways to solve this problem.
/// One surprisingly effective and straightforward solution
/// is to implement `jazzMan` as a `NoteFun`, not a `PhraseFun`.
/// In jazz, if an eighth note falls on a quarter-note beat it is
/// said to fall on the “downbeat”, and the eighth notes that are in between are
/// said to fall on the “upbeat”.
///
/// For example, in the phrase `c4 en :+: d4 en :+: e4 en :+: f4 en`,
/// the `C` and `E` fall on the downbeat, and the `D` and `F` fall
/// on the upbeat. So to get a “swing feel,” the notes on the down beat need to
/// be lengthened, and ones on the upbeat need to be delayed and shortened.
/// Whether an event falls on a downbeat or upbeat can be determined from
/// the `start_time` and `duration` of the context.)
mod jazz_man {
use std::sync::Arc;

use num_rational::Ratio;

use musik::{
music::AttrNote,
performance::{defaults::default_note_attribute_handler, Context, Event},
Dur, Performance, Player,
};

pub fn swing_play_note(
ctx: Context<AttrNote>,
dur: Dur,
(note_pitch, attrs): &AttrNote,
) -> Performance {
let Context {
start_time,
player: _ignore_player,
instrument,
whole_note,
pitch,
volume,
key: _ignore_key,
} = ctx.clone();

let number_of_beats_since_start = start_time / whole_note;
// denom belongs to {1, 2, 4}
let is_downbeat = 4 % (*number_of_beats_since_start.denom()) == 0;
let is_upbeat = number_of_beats_since_start.denom() == &8;

let (start_time, dur) = {
// only for eight notes
if dur == Dur::EN {
if is_downbeat {
(start_time, dur * Ratio::new(4, 3))
} else if is_upbeat {
let lengthened_on = Ratio::new(1, 24) * ctx.whole_note;
(start_time + lengthened_on, dur * Ratio::new(2, 3))
} else {
(start_time, dur)
}
} else {
(start_time, dur)
}
};

let init = Event {
start_time,
instrument,
pitch: note_pitch.abs() + pitch,
duration: dur.into_ratio() * whole_note,
volume,
params: vec![],
};

let event = attrs.iter().fold(init, |acc, attr| {
default_note_attribute_handler()(&ctx, attr, acc)
});
Performance::with_events(vec![event])
}

fn get_simple_swing_player() -> Player<AttrNote> {
Player {
name: "Jazz".to_string(),
play_note: Arc::new(swing_play_note),
..Player::default()
}
}

#[cfg(test)]
mod tests {
use std::{borrow::Cow, collections::HashMap};

use musik::{
instruments::StandardMidiInstrument, music::Volume, AbsPitch, Music, Octave, Pitch,
PitchClass,
};

use super::*;

#[test]
fn simple_swing() {
let oc4 = Octave::ONE_LINED;
let m: Music<AttrNote> = Music::line(
[PitchClass::C, PitchClass::D, PitchClass::E, PitchClass::F]
.into_iter()
.map(|pc| Music::note(Dur::EN, Pitch::new(pc, oc4)))
.collect(),
)
.into();

let players: HashMap<_, _> = Some(get_simple_swing_player())
.into_iter()
.map(|p| (p.name.clone(), p))
.collect();
let ctx = Context::with_player(Cow::Borrowed(players.get("Jazz").unwrap()));
let perf = m.perform(&players, ctx);

assert_eq!(
perf.into_events(),
[
Event {
start_time: Ratio::from_integer(0),
instrument: StandardMidiInstrument::AcousticGrandPiano.into(),
pitch: AbsPitch::from(48),
duration: Ratio::new(1, 3),
volume: Volume::loudest(),
params: vec![]
},
Event {
start_time: Ratio::new(1, 3),
instrument: StandardMidiInstrument::AcousticGrandPiano.into(),
pitch: AbsPitch::from(50),
duration: Ratio::new(1, 6),
volume: Volume::loudest(),
params: vec![]
},
Event {
start_time: Ratio::new(1, 2),
instrument: StandardMidiInstrument::AcousticGrandPiano.into(),
pitch: AbsPitch::from(52),
duration: Ratio::new(1, 3),
volume: Volume::loudest(),
params: vec![]
},
Event {
start_time: Ratio::new(5, 6),
instrument: StandardMidiInstrument::AcousticGrandPiano.into(),
pitch: AbsPitch::from(53),
duration: Ratio::new(1, 6),
volume: Volume::loudest(),
params: vec![]
}
]
);
}
}
}
1 change: 1 addition & 0 deletions examples/hsom-exercises/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod ch4;
mod ch5;
mod ch6;
mod ch7;
mod ch8;

fn simple<A, B, C, D, E>(x: A, y: B, z: C) -> E
where
Expand Down

0 comments on commit 4e23a5e

Please sign in to comment.