music_timer/music_timer_engine.rs
1#![allow(dead_code)]
2
3//!
4//! Easy interface for changes in music time.
5//!
6
7use super::{
8 music_time::MusicTime, music_time_counter::MusicTimeCounter, time_signature::TimeSignature,
9};
10use std::time::{Duration, SystemTime};
11
12const STRING_PANIC_TIME_FLOW: &str = "Hello John Titor, you reversed time!";
13
14/// This trait is used by `MusicTimerEngine` for callbacks in changes of music time.
15/// Invoke it to make the most of the performance engine.
16pub trait MusicTimerState {
17 /// Called when the beat interval changes.
18 ///
19 /// # Arguments
20 /// - `current_time` - The current time at which this callback has been triggered.
21 fn on_beat_interval(&mut self, current_time: &MusicTime);
22
23 /// Called when the beat changes.
24 ///
25 /// # Arguments
26 /// - `current_time` - The current time at which this callback has been triggered.
27 fn on_beat(&mut self, current_time: &MusicTime);
28
29 /// Called when the bar changes
30 ///
31 /// # Arguments
32 /// - `current_time` - The current time at which this callback has been triggered.
33 fn on_bar(&mut self, current_time: &MusicTime);
34}
35
36/// The engine uses all of this crate's utilities to allow to use of a music
37/// performance state system that triggers callbacks. Its aims are to allow
38/// for an easy interface for changes in music time.
39pub struct MusicTimerEngine {
40 total_time: Duration,
41 previous_time: Duration,
42 start_time: SystemTime,
43 event_trigger_time: Duration,
44 music_counter: MusicTimeCounter,
45 event_trigger_target: Duration,
46 previous_music_time: MusicTime,
47}
48
49impl MusicTimerEngine {
50 /// Create a new `MusicTimerEngine` with a `TimeSignature` and bpm.
51 ///
52 /// # Arguments
53 /// * `time_signature` - The time signature for the performance.
54 /// * `bpm` - The beats per minute used for the performance.
55 ///
56 /// # Example
57 /// ```
58 /// use music_timer::{music_timer_engine::MusicTimerEngine, time_signature::TimeSignature};
59 /// let mut performer = MusicTimerEngine::new(TimeSignature::new(3, 4), 155.0);
60 /// ```
61 pub fn new(time_signature: TimeSignature, bpm: f32) -> Self {
62 let music_counter = MusicTimeCounter::new(time_signature);
63 let event_trigger_target = music_counter.beat_interval_target_frames(bpm);
64 MusicTimerEngine {
65 total_time: Duration::default(),
66 previous_time: Duration::default(),
67 start_time: SystemTime::now(),
68 event_trigger_time: event_trigger_target,
69 music_counter,
70 event_trigger_target,
71 previous_music_time: MusicTime::new(0, 0, 0),
72 }
73 }
74
75 /// Pulse the engine. The time since the last pulse is used to evaluate if there is
76 /// a change in music time. It is suggested to call this from a loop.
77 ///
78 /// # Arguments
79 /// * `state` - The _trait_ `MusicTimerState` used for changes in music time callbacks.TimeSignature
80 ///
81 /// # Example
82 /// ```
83 /// use music_timer::{music_timer_engine::{MusicTimerEngine, MusicTimerState}, music_time::MusicTime};
84 /// struct PerformanceState;
85 /// impl MusicTimerState for PerformanceState {
86 /// fn on_beat_interval(&mut self, current_time: &MusicTime) {
87 /// // Do something on the beat interval
88 /// }
89 /// fn on_beat(&mut self, current_time: &MusicTime) {
90 /// // Do something on the beat
91 /// }
92 /// fn on_bar(&mut self, current_time: &MusicTime) {
93 /// // Do something on the bar
94 /// }
95 /// }
96 /// let mut performer_state = PerformanceState{};
97 /// let mut performer = music_timer::create_performance_engine(3, 4, 155.0);
98 /// performer.pulse(&mut performer_state);
99 /// ```
100 pub fn pulse<TimerState: MusicTimerState>(&mut self, state: &mut TimerState) {
101 // Progress total time
102 self.previous_time = self.total_time;
103 // Time should never reverse else you're in trouble
104 self.total_time = SystemTime::now()
105 .duration_since(self.start_time)
106 .expect(STRING_PANIC_TIME_FLOW);
107
108 // Advance by delta
109 let time_delta = self.total_time - self.previous_time;
110 self.event_trigger_time += time_delta;
111
112 // Check for an advance in the beat interval
113 let is_beat_interval_advanced = self.event_trigger_time >= self.event_trigger_target;
114 if is_beat_interval_advanced {
115 let current_time = self.music_counter.current_time();
116
117 // On beat interval change
118 state.on_beat_interval(¤t_time);
119
120 // On beat change
121 let is_beat_changed =
122 self.previous_music_time.get_beat() != self.music_counter.current_time().get_beat();
123 if is_beat_changed {
124 state.on_beat(¤t_time);
125 }
126
127 // On bar change
128 let is_bar_changed =
129 self.previous_music_time.get_bar() != self.music_counter.current_time().get_bar();
130 if is_bar_changed {
131 state.on_bar(¤t_time);
132 }
133
134 // Advance and store time
135 self.previous_music_time = self.music_counter.current_time().clone();
136 self.music_counter.advance_beat_interval();
137
138 // Reset and calibrate drift - https://www.youtube.com/watch?v=Gm7lcZiLOus&t=30s
139 let initial_d = self.event_trigger_time - self.event_trigger_target;
140 self.event_trigger_time = initial_d;
141 }
142 }
143
144 /// Gets the duration of time between beat intervals. Handy for sleeping threads.
145 ///
146 /// # Example
147 /// ```
148 /// let mut performer = music_timer::create_performance_engine(3, 4, 155.0);
149 ///
150 /// // We can set the delay to be half the trigger target. This will give
151 /// // us a reasonable cycle speed with enough buffer to keep an accurate time.
152 /// // This of course is not needed if the application is managing thread sleeping.
153 /// // The shorter the sleep duration of the thread, the more accurate the
154 /// // time triggering will be. In most cases setting the sleep to 60fps is recommended for
155 /// // < 180bpm @ 4/4.
156 /// let sleep_duration = performer.get_beat_interval_duration() / 2;
157 /// println!("SLEEP_DURATION: {:?}", sleep_duration);
158 /// std::thread::sleep(sleep_duration);
159 /// ```
160 ///
161 pub fn get_beat_interval_duration(&self) -> Duration {
162 self.event_trigger_target
163 }
164
165 /// Gets the current music time of the performance.
166 pub fn get_current_time(&self) -> &MusicTime {
167 self.music_counter.current_time()
168 }
169
170 /// Sets the current music time.
171 ///
172 /// # Arguments
173 /// * `time` - The new music time to set.
174 pub fn set_music_timer(&mut self, time: MusicTime) -> &mut Self {
175 self.music_counter.set_current_time(time);
176 self
177 }
178}