Files: a2de68cc1a716fedcf010549ceed0b77aebe92f1 / src / clock.rs
8957 bytesRaw
1 | // inspired by https://github.com/mmckegg/rust-loop-drop/blob/master/src/midi_time.rs |
2 | // http://www.deluge.co/?q=midi-tempo-bpm |
3 | |
4 | use std::u64; |
5 | use std::time::{Duration, Instant}; |
6 | use std::thread::{sleep, spawn}; |
7 | use std::sync::mpsc::{channel, Sender, Receiver, TryRecvError}; |
8 | |
9 | use control; |
10 | |
11 | pub type Nanos = u64; |
12 | pub type Ticks = u64; |
13 | pub type Beats = u64; |
14 | pub type Bars = u64; |
15 | |
16 | static SECONDS_PER_MINUTE: u64 = 60; |
17 | static NANOS_PER_SECOND: u64 = 1_000_000_000; |
18 | static BEATS_PER_MINUTE: u64 = 60; |
19 | static DEFAULT_TICKS_PER_BEAT: u64 = 16; |
20 | static DEFAULT_BEATS_PER_BAR: u64 = 4; |
21 | static DEFAULT_BARS_PER_LOOP: u64 = 4; |
22 | static DEFAULT_BEATS_PER_MINUTE: f64 = 60_f64; |
23 | |
24 | |
25 | pub struct ClockSignature { |
26 | pub nanos_per_beat: u64, // tempo |
27 | pub ticks_per_beat: u64, // meter |
28 | pub beats_per_bar: u64, // meter |
29 | pub bars_per_loop: u64, |
30 | } |
31 | |
32 | impl ClockSignature { |
33 | pub fn new (nanos_per_beat: u64) -> Self { |
34 | Self { |
35 | nanos_per_beat: nanos_per_beat, |
36 | ticks_per_beat: DEFAULT_TICKS_PER_BEAT, |
37 | beats_per_bar: DEFAULT_BEATS_PER_BAR, |
38 | bars_per_loop: DEFAULT_BARS_PER_LOOP, |
39 | } |
40 | } |
41 | |
42 | pub fn default () -> Self { |
43 | Self::from_beats_per_minute(DEFAULT_BEATS_PER_MINUTE) |
44 | } |
45 | |
46 | pub fn from_beats_per_minute (beats_per_minute: f64) -> Self { |
47 | let minutes_per_beat = 1_f64 / beats_per_minute; |
48 | let seconds_per_beat = minutes_per_beat * SECONDS_PER_MINUTE as f64; |
49 | let nanos_per_beat = seconds_per_beat * NANOS_PER_SECOND as f64; |
50 | Self::new(nanos_per_beat as u64) |
51 | } |
52 | |
53 | pub fn to_beats_per_minute (&self) -> f64 { |
54 | let nanos_per_beat = self.nanos_per_beat; |
55 | let beats_per_nano = 1_f64 / self.nanos_per_beat as f64; |
56 | let beats_per_second = beats_per_nano * NANOS_PER_SECOND as f64; |
57 | let beats_per_minute = beats_per_second * SECONDS_PER_MINUTE as f64; |
58 | beats_per_minute |
59 | } |
60 | |
61 | pub fn nanos_per_tick (&self) -> u64 { |
62 | (self.nanos_per_beat / self.ticks_per_beat) as u64 |
63 | } |
64 | |
65 | pub fn nanos_per_beat (&self) -> u64 { |
66 | self.nanos_per_beat |
67 | } |
68 | |
69 | pub fn nanos_per_bar (&self) -> u64 { |
70 | self.nanos_per_beat() * self.beats_per_bar |
71 | } |
72 | |
73 | pub fn nanos_per_loop (&self) -> u64 { |
74 | self.nanos_per_bar() * self.bars_per_loop |
75 | } |
76 | |
77 | pub fn nanos_to_ticks (&self, nanos: Nanos) -> u64 { |
78 | (nanos / self.nanos_per_tick()) % self.ticks_per_beat |
79 | } |
80 | |
81 | pub fn nanos_to_beats (&self, nanos: Nanos) -> u64 { |
82 | (nanos / self.nanos_per_beat()) % self.beats_per_bar |
83 | } |
84 | |
85 | pub fn nanos_to_bars (&self, nanos: Nanos) -> u64 { |
86 | nanos / self.nanos_per_bar() % self.bars_per_loop |
87 | } |
88 | } |
89 | |
90 | |
91 | pub struct ClockTime { |
92 | nanos: Nanos, |
93 | signature: ClockSignature |
94 | } |
95 | |
96 | impl ClockTime { |
97 | pub fn new (nanos: Nanos, signature: ClockSignature) -> Self { |
98 | Self { |
99 | nanos, |
100 | signature |
101 | } |
102 | } |
103 | |
104 | pub fn nanos (&self) -> Nanos { |
105 | self.nanos |
106 | } |
107 | |
108 | pub fn ticks (&self) -> Ticks { |
109 | self.signature.nanos_to_ticks(self.nanos) |
110 | } |
111 | |
112 | pub fn beats (&self) -> Beats { |
113 | self.signature.nanos_to_beats(self.nanos) |
114 | } |
115 | |
116 | pub fn bars (&self) -> Bars { |
117 | self.signature.nanos_to_bars(self.nanos) |
118 | } |
119 | |
120 | pub fn nanos_since_loop (&self) -> Nanos { |
121 | self.nanos % self.signature.nanos_per_loop() |
122 | } |
123 | |
124 | pub fn nanos_since_tick (&self) -> Nanos { |
125 | self.nanos % self.signature.nanos_per_tick() |
126 | } |
127 | |
128 | pub fn nanos_since_beat (&self) -> Nanos { |
129 | self.nanos % self.signature.nanos_per_beat() |
130 | } |
131 | |
132 | pub fn nanos_since_bar (&self) -> Nanos { |
133 | self.nanos % self.signature.nanos_per_bar() |
134 | } |
135 | } |
136 | |
137 | |
138 | pub struct Clock { |
139 | tick: Instant, |
140 | tap: Option<Instant>, |
141 | nanos: Nanos, |
142 | signature: ClockSignature |
143 | } |
144 | |
145 | pub enum ClockMessage { |
146 | NudgeTempo(f64), |
147 | Reset, |
148 | Signature(ClockSignature), |
149 | Tap, |
150 | } |
151 | |
152 | impl Clock { |
153 | pub fn new () -> Self { |
154 | let tick = Instant::now(); |
155 | let signature = ClockSignature::default(); |
156 | |
157 | Self { |
158 | nanos: 0, |
159 | tap: None, |
160 | tick, |
161 | signature |
162 | } |
163 | } |
164 | |
165 | pub fn start (control_tx: Sender<control::ControlMessage>) -> Sender<ClockMessage> { |
166 | let mut clock = Self::new(); |
167 | |
168 | let (tx, rx) = channel(); |
169 | |
170 | control_tx.send(control::ControlMessage::Signature(ClockSignature::from_beats_per_minute(DEFAULT_BEATS_PER_MINUTE))).unwrap(); |
171 | |
172 | spawn(move|| { |
173 | loop { |
174 | // wait a tick |
175 | let diff = clock.tick(); |
176 | |
177 | // send clock time |
178 | control_tx.send(control::ControlMessage::Time(clock.time())).unwrap(); |
179 | |
180 | // handle any incoming messages |
181 | let message_result = rx.try_recv(); |
182 | match message_result { |
183 | Ok(ClockMessage::Reset) => { |
184 | clock.reset(); |
185 | }, |
186 | Ok(ClockMessage::Signature(signature)) => { |
187 | clock.signature = signature; |
188 | }, |
189 | Ok(ClockMessage::Tap) => { |
190 | // find how far off the beat we are |
191 | let time = clock.time(); |
192 | let nanos_since_beat = time.nanos_since_beat(); |
193 | let nanos_per_beat = time.signature.nanos_per_beat(); |
194 | let nanos_per_half_beat = time.signature.nanos_per_beat() / 2; |
195 | // if the beat happened recently |
196 | if nanos_since_beat < nanos_per_half_beat { |
197 | // nudge back to the beat |
198 | clock.nanos = time.nanos - nanos_since_beat |
199 | } else { |
200 | // nudge to the next beat |
201 | clock.nanos = time.nanos + nanos_per_beat - nanos_since_beat |
202 | } |
203 | |
204 | // if second tap on beat, adjust tempo |
205 | match clock.tap { |
206 | Some(tap) => { |
207 | let tap_diff = duration_to_nanos(tap.elapsed()); |
208 | if tap_diff < (nanos_per_beat * 2) { |
209 | let next_signature = ClockSignature::new(tap_diff); |
210 | control_tx.send(control::ControlMessage::Signature(next_signature)); |
211 | } |
212 | }, |
213 | None => {} |
214 | } |
215 | |
216 | clock.tap = Some(Instant::now()); |
217 | }, |
218 | Ok(ClockMessage::NudgeTempo(nudge)) => { |
219 | let old_beats_per_minute = clock.signature.to_beats_per_minute(); |
220 | let new_beats_per_minute = old_beats_per_minute + nudge; |
221 | let next_signature = ClockSignature::from_beats_per_minute(new_beats_per_minute); |
222 | control_tx.send(control::ControlMessage::Signature(next_signature)); |
223 | }, |
224 | Err(TryRecvError::Empty) => {}, |
225 | Err(TryRecvError::Disconnected) => { |
226 | panic!("{:?}", TryRecvError::Disconnected); |
227 | } |
228 | } |
229 | } |
230 | }); |
231 | |
232 | tx |
233 | } |
234 | |
235 | pub fn reset (&mut self) { |
236 | self.nanos = 0; |
237 | self.tick = Instant::now(); |
238 | self.tap = None; |
239 | } |
240 | |
241 | pub fn time (&self) -> ClockTime { |
242 | ClockTime::new(self.nanos_since_loop(), self.signature) |
243 | } |
244 | |
245 | pub fn nanos_since_loop (&self) -> Nanos { |
246 | self.nanos % self.signature.nanos_per_loop() |
247 | } |
248 | |
249 | pub fn nanos_since_tick (&self) -> Nanos { |
250 | duration_to_nanos(self.tick.elapsed()) % self.signature.nanos_per_tick() |
251 | } |
252 | |
253 | pub fn nanos_until_tick (&self) -> Nanos { |
254 | let nanos_since_tick = self.nanos_since_tick(); |
255 | let nanos_per_tick = self.signature.nanos_per_tick(); |
256 | nanos_per_tick - nanos_since_tick |
257 | } |
258 | |
259 | // https://github.com/BookOwl/fps_clock/blob/master/src/lib.rs |
260 | pub fn tick (&mut self) -> Nanos { |
261 | let nanos_until_tick = self.nanos_until_tick(); |
262 | |
263 | sleep(Duration::new(0, nanos_until_tick as u32)); |
264 | |
265 | self.nanos = self.nanos + nanos_until_tick; |
266 | self.tick = Instant::now(); |
267 | |
268 | nanos_until_tick |
269 | } |
270 | } |
271 | |
272 | fn duration_to_nanos (duration: Duration) -> Nanos { |
273 | duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as Nanos |
274 | } |
275 | |
276 | /* |
277 | pub fn nanos_from_ticks (ticks: Ticks, signature: ClockSignature) -> Nanos { |
278 | ticks * signature.nanos_per_beat |
279 | } |
280 | |
281 | pub fn ticks_from_beats (beats: Beats, signature: ClockSignature) -> Ticks { |
282 | beats * signature.ticks_per_beat |
283 | } |
284 | |
285 | pub fn ticks_from_measure (measures: Measures, signature: ClockSignature) -> Ticks { |
286 | measures * signature.beats_per_measure * signature.ticks_per_beat |
287 | } |
288 | */ |
289 |
Built with git-ssb-web