Commit fca343292fb72c697c29294488b5b36397ac1dc5
gotta catch the beat
Michael Williams committed on 5/13/2018, 11:47:46 AMParent: f675cb27f2640ba304b1816b279b2ef7f8c19a59
Files changed
README.md | changed |
src/clock.rs | changed |
src/control.rs | changed |
src/interface.rs | changed |
src/main.rs | changed |
README.md | ||
---|---|---|
@@ -1,5 +1,26 @@ | ||
1 | -## notes | |
1 … | +# metronome | |
2 | 2 … | |
3 … | +a simple metronome in rust | |
4 … | + | |
5 … | +_work in progress_ | |
6 … | + | |
7 … | +## usage | |
8 … | +install rust | |
9 … | + | |
10 … | +> on debian: | |
11 … | +> | |
12 … | +> ``` | |
13 … | +> sudo apt install libncurses5-dev libncursesw5-dev | |
14 … | +> ``` | |
15 … | + | |
3 | 16 … | ```shell |
4 | -sudo apt install libncursesw5-dev | |
17 … | +git clone https://github.com/ahdinosaur/metronome | |
18 … | +cd metronome | |
19 … | +cargo run | |
5 | 20 … | ``` |
21 … | + | |
22 … | +controls: | |
23 … | + | |
24 … | +- press enter to reset time | |
25 … | +- press up and down to nudge tempo | |
26 … | +- TODO press space to tap tempo |
src/clock.rs | |||
---|---|---|---|
@@ -2,9 +2,9 @@ | |||
2 | 2 … | // http://www.deluge.co/?q=midi-tempo-bpm | |
3 | 3 … | ||
4 | 4 … | use std::time::{Duration, Instant}; | |
5 | 5 … | use std::thread::{sleep, spawn}; | |
6 | -use std::sync::mpsc::{channel, Sender, Receiver}; | ||
6 … | +use std::sync::mpsc::{channel, Sender, Receiver, TryRecvError}; | ||
7 | 7 … | ||
8 | 8 … | use control; | |
9 | 9 … | ||
10 | 10 … | pub type Time = Instant; | |
@@ -19,8 +19,9 @@ | |||
19 | 19 … | static BEATS_PER_MINUTE: u64 = 60; | |
20 | 20 … | static DEFAULT_TICKS_PER_BEAT: u64 = 16; | |
21 | 21 … | static DEFAULT_BEATS_PER_BAR: u64 = 4; | |
22 | 22 … | static DEFAULT_BARS_PER_LOOP: u64 = 4; | |
23 … | +static DEFAULT_BEATS_PER_MINUTE: f64 = 60_f64; | ||
23 | 24 … | ||
24 | 25 … | ||
25 | 26 … | pub struct ClockSignature { | |
26 | 27 … | pub nanos_per_beat: u64, // tempo | |
@@ -42,8 +43,12 @@ | |||
42 | 43 … | bars_per_loop: DEFAULT_BARS_PER_LOOP, | |
43 | 44 … | } | |
44 | 45 … | } | |
45 | 46 … | ||
47 … | + pub fn default () -> Self { | ||
48 … | + Self::new(DEFAULT_BEATS_PER_MINUTE) | ||
49 … | + } | ||
50 … | + | ||
46 | 51 … | pub fn to_beats_per_minute (&self) -> f64 { | |
47 | 52 … | let nanos_per_beat = self.nanos_per_beat; | |
48 | 53 … | let beats_per_nano = 1_f64 / self.nanos_per_beat as f64; | |
49 | 54 … | let beats_per_second = beats_per_nano * NANOS_PER_SECOND as f64; | |
@@ -104,34 +109,70 @@ | |||
104 | 109 … | signature: ClockSignature | |
105 | 110 … | } | |
106 | 111 … | ||
107 | 112 … | pub enum ClockMessage { | |
108 | - Time(ClockTime) | ||
113 … | + Reset, | ||
114 … | + NudgeTempo(f64), | ||
115 … | + Signature(ClockSignature) | ||
109 | 116 … | } | |
110 | 117 … | ||
111 | 118 … | impl Clock { | |
112 | - pub fn new (signature: ClockSignature) -> Self { | ||
119 … | + pub fn new () -> Self { | ||
113 | 120 … | let start = Time::now(); | |
114 | - | ||
121 … | + let signature = ClockSignature::default(); | ||
122 … | + | ||
115 | 123 … | Self { | |
116 | 124 … | start, | |
117 | 125 … | tick: start, | |
118 | 126 … | signature | |
119 | 127 … | } | |
120 | 128 … | } | |
121 | 129 … | ||
122 | - pub fn start (signature: ClockSignature, control_tx: Sender<control::ControlMessage>) { | ||
123 | - let mut clock = Self::new(signature); | ||
130 … | + pub fn start (control_tx: Sender<control::ControlMessage>) -> Sender<ClockMessage> { | ||
131 … | + let mut clock = Self::new(); | ||
124 | 132 … | ||
133 … | + let (tx, rx) = channel(); | ||
134 … | + | ||
135 … | + control_tx.send(control::ControlMessage::Signature(ClockSignature::new(DEFAULT_BEATS_PER_MINUTE))).unwrap(); | ||
136 … | + | ||
125 | 137 … | spawn(move|| { | |
126 | 138 … | loop { | |
127 | - clock.tick(); | ||
139 … | + // wait a tick | ||
140 … | + let diff = clock.tick(); | ||
128 | 141 … | ||
142 … | + // send clock time | ||
129 | 143 … | control_tx.send(control::ControlMessage::Time(clock.time())).unwrap(); | |
144 … | + | ||
145 … | + // handle any incoming messages | ||
146 … | + let message_result = rx.try_recv(); | ||
147 … | + match message_result { | ||
148 … | + Ok(ClockMessage::Reset) => { | ||
149 … | + clock.reset(); | ||
150 … | + }, | ||
151 … | + Ok(ClockMessage::Signature(signature)) => { | ||
152 … | + clock.signature = signature; | ||
153 … | + }, | ||
154 … | + Ok(ClockMessage::NudgeTempo(nudge)) => { | ||
155 … | + let old_beats_per_minute = clock.signature.to_beats_per_minute(); | ||
156 … | + let new_beats_per_minute = old_beats_per_minute - nudge; | ||
157 … | + let next_signature = ClockSignature::new(new_beats_per_minute); | ||
158 … | + control_tx.send(control::ControlMessage::Signature(next_signature)); | ||
159 … | + }, | ||
160 … | + Err(TryRecvError::Empty) => {}, | ||
161 … | + Err(TryRecvError::Disconnected) => { | ||
162 … | + panic!("{:?}", TryRecvError::Disconnected); | ||
163 … | + } | ||
164 … | + } | ||
130 | 165 … | } | |
131 | 166 … | }); | |
167 … | + | ||
168 … | + tx | ||
132 | 169 … | } | |
133 | 170 … | ||
171 … | + pub fn reset (&mut self) { | ||
172 … | + self.start = Time::now(); | ||
173 … | + } | ||
174 … | + | ||
134 | 175 … | pub fn time (&self) -> ClockTime { | |
135 | 176 … | ClockTime::new(self.nanos_since_start(), self.signature) | |
136 | 177 … | } | |
137 | 178 … |
src/control.rs | ||
---|---|---|
@@ -18,16 +18,19 @@ | ||
18 | 18 … | } |
19 | 19 … | } |
20 | 20 … | } |
21 | 21 … | |
22 | - | |
22 … | + | |
23 | 23 … | pub enum ControlMessage { |
24 | 24 … | Time(clock::ClockTime), |
25 | 25 … | Signature(clock::ClockSignature), |
26 | - Start, | |
26 … | + Reset, | |
27 … | + NudgeTempo(f64), | |
28 … | + TapTempo | |
29 … | + /* | |
27 | 30 … | Stop, |
28 | - TapTempo, | |
29 | 31 … | SetTempo, |
30 | 32 … | NudgeClock, |
31 | 33 … | Configure |
34 … | + */ | |
32 | 35 … | } |
33 | 36 … |
src/interface.rs | ||
---|---|---|
@@ -12,19 +12,15 @@ | ||
12 | 12 … | static CHAR_RETURN: u32 = 0x000D; |
13 | 13 … | static CHAR_NEWLINE: u32 = 0x000A; |
14 | 14 … | |
15 | 15 … | |
16 | -pub struct TerminalInterface { | |
17 | - pub tx: Sender<InterfaceMessage> | |
18 | -} | |
16 … | +pub struct TerminalInterface {} | |
19 | 17 … | |
20 | 18 … | impl TerminalInterface { |
21 | - pub fn start (signature: clock::ClockSignature, control_tx: Sender<control::ControlMessage>) -> Self { | |
19 … | + pub fn start (control_tx: Sender<control::ControlMessage>) -> Sender<InterfaceMessage> { | |
22 | 20 … | let (tx, rx) = channel(); |
23 | 21 … | |
24 | - let interface = Self { | |
25 | - tx | |
26 | - }; | |
22 … | + let mut signature = clock::ClockSignature::default(); | |
27 | 23 … | |
28 | 24 … | spawn(move|| { |
29 | 25 … | /* Setup ncurses. */ |
30 | 26 … | ncurses::initscr(); |
@@ -43,19 +39,31 @@ | ||
43 | 39 … | let ch = ncurses::wget_wch(ncurses::stdscr()); |
44 | 40 … | |
45 | 41 … | match ch { |
46 | 42 … | Some(WchResult::KeyCode(ncurses::KEY_MOUSE)) => { |
47 | - control_tx.send(control::ControlMessage::TapTempo).unwrap(); | |
48 | 43 … | } |
49 | 44 … | |
45 … | + Some(WchResult::KeyCode(ncurses::KEY_UP)) => { // why is up down? | |
46 … | + control_tx.send(control::ControlMessage::NudgeTempo(-1_f64)).unwrap(); | |
47 … | + } | |
48 … | + | |
49 … | + Some(WchResult::KeyCode(ncurses::KEY_DOWN)) => { // why is down up? | |
50 … | + control_tx.send(control::ControlMessage::NudgeTempo(1_f64)).unwrap(); | |
51 … | + } | |
52 … | + | |
50 | 53 … | // https://github.com/jeaye/ncurses-rs/blob/master/src/constants.rs |
51 | - Some(WchResult::KeyCode(_)) => {} | |
54 … | + Some(WchResult::KeyCode(_)) => { | |
55 … | + } | |
52 | 56 … | |
53 | 57 … | // Some(WchResult::KeyCode(KEY_ENTER)) => beat(), |
54 | 58 … | Some(WchResult::Char(ch)) => { |
55 | - if (ch == CHAR_SPACE || ch == CHAR_NEWLINE) { | |
59 … | + if (ch == CHAR_SPACE) { | |
56 | 60 … | control_tx.send(control::ControlMessage::TapTempo).unwrap(); |
57 | 61 … | } |
62 … | + | |
63 … | + if (ch == CHAR_NEWLINE) { | |
64 … | + control_tx.send(control::ControlMessage::Reset).unwrap(); | |
65 … | + } | |
58 | 66 … | } |
59 | 67 … | |
60 | 68 … | None => {} |
61 | 69 … | } |
@@ -76,17 +84,18 @@ | ||
76 | 84 … | print_bar(time); |
77 | 85 … | print_time(time); |
78 | 86 … | print_signature(signature); |
79 | 87 … | }, |
80 | - InterfaceMessage::Signature(signature) => { | |
88 … | + InterfaceMessage::Signature(next_signature) => { | |
89 … | + signature = next_signature; | |
81 | 90 … | } |
82 | 91 … | } |
83 | 92 … | |
84 | 93 … | ncurses::refresh(); |
85 | 94 … | } |
86 | 95 … | }); |
87 | 96 … | |
88 | - interface | |
97 … | + tx | |
89 | 98 … | } |
90 | 99 … | } |
91 | 100 … | |
92 | 101 … | pub fn print_beat (time: clock::ClockTime) { |
src/main.rs | ||
---|---|---|
@@ -8,30 +8,37 @@ | ||
8 | 8 … | Metronome::run(60_f64); |
9 | 9 … | } |
10 | 10 … | |
11 | 11 … | |
12 | -pub type Bpm = f64; | |
12 … | +pub type Tempo = f64; | |
13 | 13 … | |
14 | -struct Metronome { | |
15 | - pub bpm: Bpm | |
16 | -} | |
14 … | +struct Metronome {} | |
17 | 15 … | |
18 | 16 … | impl Metronome { |
19 | - pub fn run (bpm: Bpm) { | |
17 … | + pub fn run (tempo: Tempo) { | |
20 | 18 … | let control = control::Control::new(); |
21 | 19 … | |
22 | - let clock_signature = clock::ClockSignature::new(bpm); | |
20 … | + let terminal_tx = interface::TerminalInterface::start(control.tx.clone()); | |
21 … | + let clock_tx = clock::Clock::start(control.tx.clone()); | |
23 | 22 … | |
24 | - let clock = clock::Clock::start(clock_signature, control.tx.clone()); | |
25 | - let terminal_interface = interface::TerminalInterface::start(clock_signature, control.tx.clone()); | |
26 | - | |
27 | 23 … | for control_message in control.rx { |
28 | 24 … | match control_message { |
25 … | + // sent by interface | |
26 … | + control::ControlMessage::Reset => { | |
27 … | + clock_tx.send(clock::ClockMessage::Reset).unwrap(); | |
28 … | + }, | |
29 … | + // sent by interface | |
30 … | + control::ControlMessage::NudgeTempo(nudge) => { | |
31 … | + clock_tx.send(clock::ClockMessage::NudgeTempo(nudge)).unwrap(); | |
32 … | + }, | |
33 … | + // sent by clock | |
29 | 34 … | control::ControlMessage::Signature(signature) => { |
30 | - terminal_interface.tx.send(interface::InterfaceMessage::Signature(signature)).unwrap(); | |
35 … | + clock_tx.send(clock::ClockMessage::Signature(signature)).unwrap(); | |
36 … | + terminal_tx.send(interface::InterfaceMessage::Signature(signature)).unwrap(); | |
31 | 37 … | }, |
38 … | + // sent by clock | |
32 | 39 … | control::ControlMessage::Time(time) => { |
33 | - terminal_interface.tx.send(interface::InterfaceMessage::Time(time)).unwrap(); | |
40 … | + terminal_tx.send(interface::InterfaceMessage::Time(time)).unwrap(); | |
34 | 41 … | }, |
35 | 42 … | _ => {} |
36 | 43 … | } |
37 | 44 … | } |
Built with git-ssb-web