1 // Copyright 2017 Emilie Gillet.
2 //
3 // Author: Emilie Gillet (emilie.o.gillet@gmail.com)
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // THE SOFTWARE.
22 //
23 // See http://creativecommons.org/licenses/MIT/ for more information.
24 //
25 // -----------------------------------------------------------------------------
26 //
27 // Recovers a ramp from a clock input by guessing at what time the next edge
28 // will occur. Prediction strategies:
29 // - Moving average of previous intervals.
30 // - Periodic rhythmic pattern.
31 // - Assume that the pulse width is constant, deduct the period from the on time
32 // and the pulse width.
33 //
34 // All prediction strategies are concurrently tested, and the output from the
35 // best performing one is selected (à la early Scheirer/Goto beat trackers).
36
37 #include "tides2/ramp_extractor.h"
38
39 #include <algorithm>
40
41 #include "stmlib/dsp/dsp.h"
42
43 namespace tides {
44
45 using namespace std;
46 using namespace stmlib;
47
48 const float kPulseWidthTolerance = 0.05f;
49
IsWithinTolerance(float x,float y,float error)50 inline bool IsWithinTolerance(float x, float y, float error) {
51 return x >= y * (1.0f - error) && x <= y * (1.0f + error);
52 }
53
Init(float sample_rate,float max_frequency)54 void RampExtractor::Init(float sample_rate, float max_frequency) {
55 max_frequency_ = max_frequency;
56 min_period_ = 1.0f / max_frequency_;
57 sample_rate_ = sample_rate;
58 Reset();
59 }
60
Reset()61 void RampExtractor::Reset() {
62 train_phase_ = 0.0f;
63 target_frequency_ = frequency_lp_ = frequency_ = 0.1f / sample_rate_;
64 period_ = int(1.0f / frequency_);
65
66 lp_coefficient_ = 0.1f;
67 max_ramp_value_ = 1.0f;
68 f_ratio_ = 1.0f;
69 reset_counter_ = 1;
70 reset_interval_ = sample_rate_ * 3;
71
72 Pulse p;
73 p.on_duration = uint32_t(sample_rate_ * 0.25f);
74 p.total_duration = uint32_t(sample_rate_ * 0.5f);
75 p.pulse_width = 0.5f;
76
77 fill(&history_[0], &history_[kHistorySize], p);
78 current_pulse_ = 0;
79 history_[current_pulse_].on_duration = 0;
80 history_[current_pulse_].total_duration = 0;
81
82 average_pulse_width_ = 0.0f;
83 fill(&prediction_error_[0], &prediction_error_[kMaxPatternPeriod + 1], 50.0f);
84 fill(&predicted_period_[0], &predicted_period_[kMaxPatternPeriod + 1],
85 sample_rate_ * 0.5f);
86 prediction_error_[0] = 0.0f;
87 }
88
ComputeAveragePulseWidth(float tolerance) const89 float RampExtractor::ComputeAveragePulseWidth(float tolerance) const {
90 float sum = 0.0f;
91 for (size_t i = 0; i < kHistorySize; ++i) {
92 if (!IsWithinTolerance(history_[i].pulse_width,
93 history_[current_pulse_].pulse_width,
94 tolerance)) {
95 return 0.0f;
96 }
97 sum += history_[i].pulse_width;
98 }
99 return sum / static_cast<float>(kHistorySize);
100 }
101
PredictNextPeriod()102 float RampExtractor::PredictNextPeriod() {
103 float last_period = static_cast<float>(
104 history_[current_pulse_].total_duration);
105
106 int best_pattern_period = 0;
107 for (int i = 0; i <= kMaxPatternPeriod; ++i) {
108 float error = predicted_period_[i] - last_period;
109 float error_sq = error * error;
110 SLOPE(prediction_error_[i], error_sq, 0.7f, 0.2f);
111
112 if (i == 0) {
113 ONE_POLE(predicted_period_[0], last_period, 0.5f);
114 } else {
115 size_t t = current_pulse_ + 1 + kHistorySize - i;
116 predicted_period_[i] = history_[t % kHistorySize].total_duration;
117 }
118
119 if (prediction_error_[i] < prediction_error_[best_pattern_period]) {
120 best_pattern_period = i;
121 }
122 }
123 return predicted_period_[best_pattern_period];
124 }
125
Process(bool smooth_audio_rate_tracking,bool force_integer_period,Ratio ratio,const GateFlags * gate_flags,float * ramp,size_t size)126 float RampExtractor::Process(
127 bool smooth_audio_rate_tracking,
128 bool force_integer_period,
129 Ratio ratio,
130 const GateFlags* gate_flags,
131 float* ramp,
132 size_t size) {
133 if (smooth_audio_rate_tracking) {
134 return ProcessInternal<true>(
135 force_integer_period, ratio, gate_flags, ramp, size);
136 } else {
137 return ProcessInternal<false>(
138 force_integer_period, ratio, gate_flags, ramp, size);
139 }
140 }
141
142 template<bool smooth_audio_rate_tracking>
ProcessInternal(bool force_integer_period,Ratio ratio,const GateFlags * gate_flags,float * ramp,size_t size)143 inline float RampExtractor::ProcessInternal(
144 bool force_integer_period,
145 Ratio ratio,
146 const GateFlags* gate_flags,
147 float* ramp,
148 size_t size) {
149 while (size--) {
150 GateFlags flags = *gate_flags++;
151 // We are done with the previous pulse.
152 if (flags & GATE_FLAG_RISING) {
153 Pulse& p = history_[current_pulse_];
154
155 const bool record_pulse = p.total_duration < reset_interval_;
156 if (!record_pulse) {
157 reset_counter_ = ratio.q;
158 train_phase_ = 0.0f;
159 f_ratio_ = ratio.ratio;
160 max_train_phase_ = static_cast<float>(ratio.q);
161 reset_interval_ = 4 * p.total_duration;
162 } else {
163 float period = float(p.total_duration);
164 if (smooth_audio_rate_tracking) {
165 bool no_glide = f_ratio_ != ratio.ratio;
166 f_ratio_ = ratio.ratio;
167
168 float frequency = 1.0f / period;
169 target_frequency_ = std::min(f_ratio_ * frequency, 0.125f);
170
171 float up_tolerance = (1.02f + 2.0f * frequency) * frequency_lp_;
172 float down_tolerance = (0.98f - 2.0f * frequency) * frequency_lp_;
173 no_glide |= target_frequency_ > up_tolerance ||
174 target_frequency_ < down_tolerance;
175 lp_coefficient_ = no_glide ? 1.0f : period * 0.00001f;
176 } else {
177 // Compute the pulse width of the previous pulse, and check if the
178 // PW has been consistent over the past pulses.
179 if (period < min_period_) {
180 frequency_ = target_frequency_ = 1.0f / period;
181 } else {
182 p.pulse_width = static_cast<float>(p.on_duration) / \
183 static_cast<float>(p.total_duration);
184 average_pulse_width_ = ComputeAveragePulseWidth(
185 kPulseWidthTolerance);
186 if (p.on_duration < 32) {
187 average_pulse_width_ = 0.0f;
188 }
189 frequency_ = target_frequency_ = 1.0f / PredictNextPeriod();
190 }
191
192 --reset_counter_;
193 if (!reset_counter_) {
194 train_phase_ = 0.0f;
195 reset_counter_ = ratio.q;
196 f_ratio_ = ratio.ratio;
197 max_train_phase_ = static_cast<float>(ratio.q);
198 } else {
199 float expected = max_train_phase_ - static_cast<float>(
200 reset_counter_);
201 float warp = expected - train_phase_ + 1.0f;
202 frequency_ *= max(warp, 0.01f);
203 }
204 }
205 reset_interval_ = static_cast<uint32_t>(
206 std::max(4.0f / target_frequency_, sample_rate_ * 3.0f));
207 current_pulse_ = (current_pulse_ + 1) % kHistorySize;
208 }
209 // Record a new pulse.
210 history_[current_pulse_].on_duration = 0;
211 history_[current_pulse_].total_duration = 0;
212 }
213
214 // Update history buffer with total duration and on duration.
215 ++history_[current_pulse_].total_duration;
216 if (flags & GATE_FLAG_HIGH) {
217 ++history_[current_pulse_].on_duration;
218 }
219
220 if (smooth_audio_rate_tracking) {
221 ONE_POLE(frequency_lp_, target_frequency_, lp_coefficient_);
222 if (force_integer_period) {
223 int new_period = int(1.0f / frequency_lp_);
224 if (abs(new_period - period_) > 1) {
225 period_ = new_period;
226 frequency_ = 1.0f / float(new_period);
227 }
228 } else {
229 frequency_ = frequency_lp_;
230 }
231 train_phase_ += frequency_;
232 if (train_phase_ >= 1.0f) {
233 train_phase_ -= 1.0f;
234 }
235 *ramp++ = train_phase_;
236 } else {
237 if ((flags & GATE_FLAG_FALLING) &&
238 average_pulse_width_ > 0.0f) {
239 float t_on = static_cast<float>(
240 history_[current_pulse_].on_duration);
241 float next = max_train_phase_ - static_cast<float>(
242 reset_counter_) + 1.0f;
243 float pw = average_pulse_width_;
244 frequency_ = max((next - train_phase_), 0.0f) * pw / \
245 ((1.0f - pw) * t_on);
246 }
247 train_phase_ += frequency_;
248 if (train_phase_ >= max_train_phase_) {
249 train_phase_ = max_train_phase_;
250 }
251 float phase = train_phase_ * f_ratio_;
252 phase -= static_cast<float>(static_cast<int32_t>(phase));
253 *ramp++ = phase;
254 }
255 }
256 return smooth_audio_rate_tracking ? frequency_ : frequency_ * f_ratio_;
257 }
258
259 } // namespace tides