24 halp_meta(name,
"Rate Limiter")
25 halp_meta(c_name,
"RateLimiter")
26 halp_meta(category,
"Control/Mappings")
28 manual_url,
"https://ossia.io/score-docs/processes/rate-limiter.html#rate-limiter")
29 halp_meta(author,
"ossia score")
32 "Limit a value stream to one value per interval, on the musical grid when "
33 "the interval is synced, or debounce after a quiet interval")
34 halp_meta(uuid,
"485d39ed-e495-448c-95df-5f2ea905ef5b")
35 halp_flag(process_exec);
46 struct : halp::time_chooser<
47 "Interval", halp::range{0., 10., 0.01}, halp::bar_sync::restart>
51 "Minimum interval between two values (synced: at most one per note "
52 "value, on the grid), or quiet time before a debounced value")
54 struct : halp::enum_t<Mode,
"Mode">
56 void update(
Node& self)
58 if(self.previous_mode != this->value)
62 struct : halp::toggle<
"Send latest", halp::default_on_toggle>
66 "When limiting, hold the latest value that comes too early and send "
67 "it when the interval is over (synced: on the next grid point); off, "
68 "drop it (synced: the first value of each grid step goes through)")
73 halp::timed_callback<
"out", ossia::value> out;
79 using tick = ossia::token_request;
80 ossia::exec_state_facade ossia_state;
82 static constexpr double flicks_per_s = 705'600'000.;
85 std::optional<ossia::value> pending;
86 long double deadline{};
87 std::optional<int64_t> previous_end;
88 Mode previous_mode{Mode::Limit};
91 ossia::small_vector<const ossia::timed_value*, 16> ordered_events;
99 previous_mode = inputs.mode.value;
102 void prepare(halp::setup)
noexcept { reset(); }
103 void start() noexcept { reset(); }
104 void stop() noexcept { reset(); }
105 void transport(
auto) noexcept { reset(); }
108 template <
typename F>
111 const auto& data = inputs.port.value->get_data();
112 const auto in_tick = [&](
const ossia::timed_value& v) {
113 return v.timestamp >= start && v.timestamp < start + frames;
115 if(std::is_sorted(data.begin(), data.end(), [](
const auto& a,
const auto& b) {
116 return a.timestamp < b.timestamp;
119 for(
const auto& v : data)
127 ordered_events.clear();
128 for(
const auto& v : data)
130 ordered_events.push_back(&v);
132 ordered_events.begin(), ordered_events.end(),
133 [](
const auto* a,
const auto* b) {
134 return a->timestamp < b->timestamp || (a->timestamp == b->timestamp && a < b);
136 for(
const auto* v : ordered_events)
141 void operator()(
const tick& t)
143 if(previous_mode != inputs.mode.value
144 || (previous_end && t.prev_date.impl != *previous_end))
146 previous_end = t.date.impl;
148 const auto [start, frames] = ossia_state.timings(t);
149 if(inputs.mode.value == Mode::Debounce)
152 if(t.date <= t.prev_date || t.speed <= 0.)
158 debounce(t, start, frames);
164 else if(!inputs.latest.value)
167 if(inputs.interval.sync)
170 limit(t, start, frames);
172 else if(inputs.interval.sync)
179 limit(t, start, frames);
187 void limit(
const tick& t, int64_t start, int64_t frames)
189 const long double begin = t.prev_date.impl;
190 const long double duration =
static_cast<long double>(t.date.impl) - begin;
191 const long double delay = std::max(0.f, inputs.interval.value) * flicks_per_s;
193 const long double slack = duration > 0. ? 1e-3L * duration / frames : 0.L;
196 const auto flush = [&](
long double date) {
197 if(!pending || !
last_sent || duration <= 0.)
199 const long double due = *
last_sent + delay;
204 = std::max(0.L, std::ceil((due - slack - begin) * frames / duration));
207 outputs.out(
static_cast<int64_t
>(frame), std::move(*pending));
213 const long double date
214 = duration > 0. ? begin + (v.timestamp - start) * duration / frames : begin;
220 outputs.out(v.timestamp - start, v.value);
222 else if(inputs.latest.value)
231 ossia::quantification_points
grid(
const tick& t)
const noexcept
236 = grid_rate(std::max(0.f, inputs.interval.value), t.tempo, inputs.interval.sync);
237 if(inputs.interval.bar_sync || !(rate > 1.))
238 return t.get_quantification_dates(rate);
241 ossia::quantification_points res;
242 const double start = t.musical_start_position;
243 const double end = t.musical_end_position;
244 const bool valid_sig = t.signature.upper > 0 && t.signature.lower > 0;
245 const double quarters_in_bar
246 = valid_sig ? 4. * t.signature.upper / t.signature.lower : 4.;
247 const double scale = double((t.date - t.prev_date).impl) / (end - start);
248 for_each_pulse_point(
249 start, end, t.musical_start_last_signature, 4. / rate, quarters_in_bar,
250 [&](
double p, int64_t index) {
252 {t.prev_date + ossia::time_value{int64_t((p - start) * scale)}, index, p});
260 const auto points = grid(t);
261 auto point = points.begin();
262 const auto emit_until = [&](int64_t frame) {
263 for(; point != points.end(); ++point)
266 = t.physical_position(point->position, ossia_state.modelToSamples());
271 outputs.out(f, std::move(*pending));
276 for_each_event(start, frames, [&](
const ossia::timed_value& v) {
278 emit_until(v.timestamp - start - 1);
289 const auto points = grid(t);
290 auto point = points.begin();
291 for_each_event(start, frames, [&](
const ossia::timed_value& v) {
292 const int64_t frame = v.timestamp - start;
293 for(; point != points.end()
294 && t.physical_position(point->position, ossia_state.modelToSamples())
297 sent_in_step =
false;
301 outputs.out(frame, v.value);
304 if(point != points.end())
305 sent_in_step =
false;
308 void debounce(
const tick& t, int64_t start, int64_t frames)
313 const long double begin = t.prev_date.impl;
314 const long double duration =
static_cast<long double>(t.date.impl) - begin;
315 const int64_t delay = int64_t(std::max(0.f, inputs.interval.value) * flicks_per_s);
316 const auto emit = [&] {
320 = std::max(0.L, std::ceil((deadline - begin) * frames / duration));
323 outputs.out(
static_cast<int64_t
>(frame), std::move(*pending));
327 for_each_event(start, frames, [&](
const ossia::timed_value& v) {
328 const auto date = begin + (v.timestamp - start) * duration / frames;
331 if(pending && deadline < date)
336 outputs.out(v.timestamp - start, v.value);
341 deadline = date + delay;
344 if(pending && deadline < t.date.impl)
350 halp_meta(layout, halp::layouts::hbox)
351 halp_meta(background, halp::colors::background_mid)
352 halp::control<&ins::interval> t;
353 halp::control<&ins::mode> mode;
354 halp::control<&ins::latest> latest;
ossia::quantification_points grid(const tick &t) const noexcept
Synced, the interval is a note value: the points of that grid in the tick.
Definition RateLimiter_v2.hpp:231
void for_each_event(int64_t start, int64_t frames, F &&f)
The input values of the tick, in time order.
Definition RateLimiter_v2.hpp:109
void limit_on_grid(const tick &t, int64_t start, int64_t frames)
The latest value goes out on each grid point.
Definition RateLimiter_v2.hpp:258