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