From 4e23a5e0296b6e9f632c7726cbd85faf43704e45 Mon Sep 17 00:00:00 2001 From: Ivan L Date: Sun, 11 Feb 2024 15:52:10 +0400 Subject: [PATCH] WIP: chapter 8: exercises --- examples/hsom-exercises/ch8.rs | 177 ++++++++++++++++++++++++++++++++ examples/hsom-exercises/main.rs | 1 + 2 files changed, 178 insertions(+) create mode 100644 examples/hsom-exercises/ch8.rs diff --git a/examples/hsom-exercises/ch8.rs b/examples/hsom-exercises/ch8.rs new file mode 100644 index 0000000..9b641e7 --- /dev/null +++ b/examples/hsom-exercises/ch8.rs @@ -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, + 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 { + 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 = 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![] + } + ] + ); + } + } +} diff --git a/examples/hsom-exercises/main.rs b/examples/hsom-exercises/main.rs index 76fbb80..46c7b79 100644 --- a/examples/hsom-exercises/main.rs +++ b/examples/hsom-exercises/main.rs @@ -7,6 +7,7 @@ mod ch4; mod ch5; mod ch6; mod ch7; +mod ch8; fn simple(x: A, y: B, z: C) -> E where