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(&current_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(&current_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(&current_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}