#music #trigger #state #time

music-timer

music-timer is a crate with music time and counting utilities featuring a callback performance engine to help with triggering events in music time. Written in Rust.

6 releases

0.1.6 Mar 15, 2020
0.1.5 Dec 12, 2019

#337 in Audio

30 downloads per month
Used in chord-composer

MIT license

34KB
406 lines

Music-Timer

Music Timer is a crate with music time and counting utilities featuring a callback performance engine to help with triggering events in music time. Written in Rust.

Its aims are to allow for an easy interface to trigger events in music time. This crate does not feature any threading.

Performance engine

To create a performance engine with a time signature and bpm call music_timer::create_performance_engine.

let mut performer = music_timer::create_performance_engine(3, 4, 155.0);

Create a struct with the trait MusicTimerState and pass it into the engine's pulse. The callbacks will be triggered in music time.

struct PerformanceState;
impl MusicTimerState for PerformanceState {
    fn on_beat_interval(&mut self, current_time: &MusicTime) {
      // Do something on the beat interval
    }
    fn on_beat(&mut self, current_time: &MusicTime) {
        // Do something on the beat
    }
    fn on_bar(&mut self, current_time: &MusicTime) {
        // Do something on the bar
    }
}

let mut performer_state = PerformanceState{};
let mut performer = music_timer::create_performance_engine(3, 4, 155.0);
performer.pulse(&mut performer_state);

Future work

  • Support un orthodox time signatures e.g 3/5.
  • Explore the need for higher resolution beat intervals.

Example

use music_timer::{
    music_time::MusicTime,
    music_timer_engine::MusicTimerState,
};

struct PerformanceState {
    is_playing: bool,
    performance_end: MusicTime,
    events: Vec<MusicTime>,
    event_head: usize,
}

impl MusicTimerState for PerformanceState {
    fn on_beat_interval(&mut self, current_time: &MusicTime) {
        let event_triggered =
            self.event_head < self.events.len() && *current_time == self.events[self.event_head];

        // Advance the event head
        if event_triggered {
            self.event_head += 1;
        }

        // Print out esoteric data
        println!(
            "{:02}.{}.{} = {}",
            current_time.get_bar(),
            current_time.get_beat(),
            current_time.get_beat_interval(),
            event_triggered
        );

        // Check to end the performance
        self.is_playing = *current_time < self.performance_end;
    }
    fn on_beat(&mut self, _current_time: &MusicTime) {
        // Do something on the beat
    }
    fn on_bar(&mut self, _current_time: &MusicTime) {
        // Do something on the bar
    }
}

fn main() {
    use std::thread;

    // Create the performer_state with bunch of events
    let mut performer_state = PerformanceState {
        is_playing: true,
        performance_end: MusicTime::new(4, 3, 8),
        events: vec![
            MusicTime::new(1, 1, 1),
            MusicTime::new(2, 2, 5),
            MusicTime::new(4, 3, 8),
        ],
        event_head: 0,
    };

    // Run our main loop
    let mut performer = music_timer::create_performance_engine(3, 4, 155.0);

    // We can set the delay to be half the trigger target. This will give
    // us a reasonable cycle speed with enough buffer to keep an accurate time.
    // This of course is not needed if the application is managing thread sleeping.
    // The shorter the sleep duration of the thread, the more accurate the
    // time triggering will be. In most cases setting the sleep to 60fps is recommended for
    // < 180bpm @ 4/4.
    let sleep_duration = performer.get_beat_interval_duration() / 2;
    println!("SLEEP_DURATION: {:?}", sleep_duration);

    while performer_state.is_playing {
        // Pass in our performance state to trigger our on event callback functions
        performer.pulse(&mut performer_state);
        thread::sleep(sleep_duration);
    }
}

No runtime deps