1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#![allow(dead_code)]

/// Easy interface for changes in music time.
use super::{
    music_time::MusicTime, music_time_counter::MusicTimeCounter, time_signature::TimeSignature,
};
use std::time::{Duration, SystemTime};

const STRING_PANIC_TIME_FLOW: &str = "Hello John Titor, you reversed time!";

/// This trait is used by `MusicTimerEngine` for callbacks in changes of music time.
/// Invoke it to make the most of the performance engine.
pub trait MusicTimerState {
    /// Called when the beat interval changes.
    ///
    /// # Arguments
    /// - `current_time` - The current time at which this callback has been triggered.
    fn on_beat_interval(&mut self, current_time: &MusicTime);

    /// Called when the beat changes.
    ///
    /// # Arguments
    /// - `current_time` - The current time at which this callback has been triggered.
    fn on_beat(&mut self, current_time: &MusicTime);

    /// Called when the bar changes
    ///
    /// # Arguments
    /// - `current_time` - The current time at which this callback has been triggered.
    fn on_bar(&mut self, current_time: &MusicTime);
}

/// The engine uses all of this crate's utilities to allow to use of a music
/// performance state system that triggers callbacks. Its aims are to allow
/// for an easy interface for changes in music time.
pub struct MusicTimerEngine {
    total_time: Duration,
    previous_time: Duration,
    start_time: SystemTime,
    event_trigger_time: Duration,
    event_drift_time: Duration,
    music_counter: MusicTimeCounter,
    event_trigger_target: Duration,
}

impl MusicTimerEngine {
    /// Create a new `MusicTimerEngine` with a `TimeSignature` and bpm.
    ///
    /// # Arguments
    /// * `time_signature` - The time signature for the performance.
    /// * `bpm` - The beats per minute used for the performance.
    ///
    /// # Example
    /// ```
    /// use music_timer::{music_timer_engine::MusicTimerEngine, time_signature::TimeSignature};
    /// let mut performer = MusicTimerEngine::new(TimeSignature::new(3, 4), 155.0);
    /// ```
    pub fn new(time_signature: TimeSignature, bpm: f32) -> Self {
        let music_counter = MusicTimeCounter::new(time_signature);
        let event_trigger_target = music_counter.beat_interval_target_frames(bpm);
        MusicTimerEngine {
            total_time: Duration::default(),
            previous_time: Duration::default(),
            start_time: SystemTime::now(),
            event_trigger_time: event_trigger_target,
            event_drift_time: Duration::default(),
            music_counter,
            event_trigger_target,
        }
    }

    /// Pulse the engine. The time since the last pulse is used to evaluate if there is
    /// a change in music time. It is suggested to call this from a loop.
    ///
    /// # Arguments
    /// * `state` - The _trait_ `MusicTimerState` used for changes in music time callbacks.TimeSignature
    ///
    /// # Example
    /// ```
    /// use music_timer::{music_timer_engine::{MusicTimerEngine, MusicTimerState}, music_time::MusicTime};
    /// 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);
    /// ```
    pub fn pulse<TimerState: MusicTimerState>(&mut self, state: &mut TimerState) {
        // Progress total time
        self.previous_time = self.total_time;

        // Time should never reverse else you're in trouble
        self.total_time = SystemTime::now()
            .duration_since(self.start_time)
            .expect(STRING_PANIC_TIME_FLOW);

        // Advance by delta
        self.event_trigger_time += self.total_time - self.previous_time;

        // Check for an advance in the beat interval
        let is_beat_interval_advanced = self.event_trigger_time >= self.event_trigger_target;
        if is_beat_interval_advanced {
            let cache_time = self.music_counter.current_time().clone();
            state.on_beat_interval(&cache_time);

            let now_time = {
                self.music_counter.advance_beat_interval();
                self.music_counter.current_time()
            };

            let is_beat_changed = cache_time.get_beat() != now_time.get_beat();
            if is_beat_changed {
                state.on_beat(&now_time);
            }

            let is_bar_changed = cache_time.get_bar() != now_time.get_bar();
            if is_bar_changed {
                state.on_bar(&now_time);
            }

            // Reset and calibrate drift - https://www.youtube.com/watch?v=Gm7lcZiLOus&t=30s
            let initial_d = self.event_trigger_time - self.event_trigger_target;
            self.event_drift_time = initial_d;
            self.event_trigger_time = initial_d;
        }
    }

    /// Gets the duration of time between beat intervals. Handy for sleeping threads.
    ///
    /// # Example
    /// ```
    /// 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);
    /// std::thread::sleep(sleep_duration);
    /// ```
    ///
    pub fn get_beat_interval_duration(&self) -> Duration {
        self.event_trigger_target
    }
}