-
Notifications
You must be signed in to change notification settings - Fork 3
/
tempo.cpp
113 lines (90 loc) · 3.91 KB
/
tempo.cpp
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
#include <Arduino.h>
#include "tempo.h"
uint16_t rising_edge_times[16] = {0}; // frames (worth 8ms each at 125fps)
uint16_t rising_edge_gap[16] = {0}; // frames. TOOD: work out if this worth 32 bytes of RAM, or if it would be better to calculate these on demand (used only in record_rising_edge!)
uint8_t edge_index = 0;
uint16_t beat_gap_sum = 0; // frames
uint16_t beat_gap_avg = 0; // frames
uint16_t next_on_frame = 0;
uint16_t next_off_frame = 0;
static bool cleared = true;
void setup_tempo() {
}
void clear_tempo() {
cleared = true;
edge_index = 0;
beat_gap_sum = 0;
beat_gap_avg = 0;
rising_edge_times[0] = F.frame_counter;
rising_edge_times[1] = F.frame_counter;
next_on_frame = F.frame_counter;
next_off_frame = F.frame_counter;
for(uint8_t i = 0; i < 16; i++) {
// rising_edge_times[i] = 0;
rising_edge_gap[i] = 0;
}
}
// records the rising edge of a beat, filtering edges that are too close together
void record_rising_edge() {
uint16_t gap = F.frame_counter - rising_edge_times[edge_index];
// 160ms is slightly less than a half beat at 180bpm ((60/180/2)*1000 == ~167ms)
if(gap > (160 / FRAME_LENGTH_MILLIS)) { // don't record beats which are too close together
if(gap > MIN_BPM_FRAMES) {
gap = MIN_BPM_FRAMES;
}
edge_index = (edge_index + 1) & 15; // range 0 to 15
rising_edge_times[edge_index] = F.frame_counter;
beat_gap_sum -= rising_edge_gap[edge_index];
rising_edge_gap[edge_index] = gap;
beat_gap_sum += gap;
beat_gap_avg = beat_gap_sum/16;
// Only show tempo estimate after we have accrued enough beats to make it possibly accurate.
// Bear in mind that some of the times might be from the last "run" of beats before we cleared.
// If we used them in the average, then the average would start off far too slow!
// (We still do the average calculation, above, because it keeps the code simple)
if(cleared && edge_index == 0) {
cleared = false;
next_on_frame = F.frame_counter;
next_off_frame = F.frame_counter + BEAT_FLASH_LENGTH;
}
}
}
// This method is called once every FRAME
// The return value is 1 if frame has a tempo high, and 0 if the frame has a tempo low.
// Heuristic:
// If we're NOT cleared, but we haven't seen a real beat in a while, we assume the calculation is probably invalid and clear the tempo
// Then we see if this frame is a transition to ON or OFF. If so, we update the next transition times.
// Then, if we're currently "cleared", we return OFF (assuming that not enough beats have been recorded to predict the tempo)
// However, if we're NOT cleared, we return our calculated ON or OFF.
//
// bool is_tempo_output_high: if we were flashing an LED, was it on or off? (we only change this value at the on-off boundaries, otherwise leaving it the same)
bool recalc_tempo(bool is_tempo_output_high) {
if(cleared) {
return false;
}
if (!cleared && (
// reset if the last beat was more than 4x the average ago
(F.frame_counter - rising_edge_times[edge_index]) > (beat_gap_avg * 4) ||
// reset if the last beat was more than 4x the frames of the minimum BPM
(F.frame_counter - rising_edge_times[edge_index]) > (MIN_BPM_FRAMES * 8) // (about 5.6secs)
)) {
// we will miss four beats before we get scared and stop the tempo
clear_tempo();
return false;
}
// basic on/off flash to the tempo
if(F.frame_counter == next_on_frame) {
// TODO: drift adjustment?
next_off_frame = next_on_frame + BEAT_FLASH_LENGTH;
next_on_frame = next_on_frame + beat_gap_avg;
is_tempo_output_high = true;
} else if(F.frame_counter == next_off_frame) {
is_tempo_output_high = false;
}
// don't flash tempos that are too slow.
if(beat_gap_avg >= MIN_BPM_FRAMES) {
// turn off all the lights if BPM is too low
is_tempo_output_high = false;
}
return is_tempo_output_high;
}