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 158 159 160 161 162
#![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, 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, 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 let time_delta = self.total_time - self.previous_time; self.event_trigger_time += time_delta; // 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_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 } /// Gets the current music time of the performance. pub fn get_current_time(&self) -> &MusicTime { self.music_counter.current_time() } }