Loading...
Searching...
No Matches
RateLimiter.hpp
1#pragma once
2#include <Fx/Types.hpp>
3
4#include <ossia/dataflow/exec_state_facade.hpp>
5#include <ossia/dataflow/token_request.hpp>
6#include <ossia/dataflow/value_port.hpp>
7
8#include <halp/callback.hpp>
9#include <halp/controls.hpp>
10#include <halp/layout.hpp>
11#include <halp/meta.hpp>
12#include <halp/midi.hpp>
13
14#include <cmath>
15
16#include <algorithm>
17#include <optional>
18
19namespace Nodes::RateLimiter
20{
21struct Node
22{
23 halp_meta(name, "Rate Limiter")
24 halp_meta(c_name, "RateLimiter")
25 halp_meta(category, "Control/Mappings")
26 halp_meta(
27 manual_url, "https://ossia.io/score-docs/processes/rate-limiter.html#rate-limiter")
28 halp_meta(author, "ossia score")
29 halp_meta(
30 description,
31 "Limit and quantize a value stream, or debounce after quiet milliseconds "
32 "(debounce ignores quantization)")
33 halp_meta(uuid, "76cfd504-7c10-4bdb-a1b4-fbe449cc06f0")
34
35 enum class Mode
36 {
37 Limit,
38 Debounce
39 };
40
41 struct ins
42 {
43 ossia_port<"in", ossia::value_port> port{};
44 quant_selector<"Quantization"> quantification; // FIXME use proper time widget
45 struct : halp::hslider_i32<"Duration", halp::irange{0, 1000, 10}>
46 {
47 halp_meta(
48 description,
49 "Minimum interval in milliseconds, or quiet time before a debounced value")
50 } ms;
51 struct : halp::enum_t<Mode, "Mode">
52 {
53 void update(Node& self)
54 {
55 if(self.previous_mode != this->value)
56 self.reset();
57 }
58 } mode;
59 } inputs;
60 struct
61 {
62 halp::timed_callback<"out", ossia::value> out;
63 } outputs;
64
65 // Raw value ports carry buffer-relative timestamps, whereas timed callbacks
66 // take tick-relative timestamps. The native token retains the slice offset
67 // that tick_flicks cannot represent.
68 using tick = ossia::token_request;
69 ossia::exec_state_facade ossia_state;
70
71 static constexpr int64_t flicks_per_ms = 705'600;
72 int64_t last_time{};
73 std::optional<ossia::value> pending;
74 long double deadline{};
75 std::optional<int64_t> previous_end;
76 Mode previous_mode{Mode::Limit};
77 ossia::small_vector<const ossia::timed_value*, 16> ordered_events;
78
79 void reset() noexcept
80 {
81 pending.reset();
82 previous_end.reset();
83 last_time = 0;
84 previous_mode = inputs.mode.value;
85 }
86
87 void prepare(halp::setup) noexcept { reset(); }
88 void start() noexcept { reset(); }
89 void stop() noexcept { reset(); }
90 void transport(auto) noexcept { reset(); }
91
92 bool should_output(float quantif, int64_t ms, const tick& t)
93 {
94 if(quantif != 0.)
95 return true;
96 else if(t.date.impl >= (last_time + ms * flicks_per_ms))
97 {
98 last_time = t.date.impl;
99 return true;
100 }
101 return false;
102 }
103
104 void operator()(const tick& t)
105 {
106 if(previous_mode != inputs.mode.value
107 || (previous_end && t.prev_date.impl != *previous_end))
108 reset();
109 previous_end = t.date.impl;
110
111 const auto [start, frames] = ossia_state.timings(t);
112 if(inputs.mode.value == Mode::Debounce)
113 {
114 // A rewind or stopped transport cannot carry a forward-time deadline.
115 if(t.date <= t.prev_date || t.speed <= 0.)
116 {
117 pending.reset();
118 return;
119 }
120 if(frames <= 0)
121 return;
122 debounce(t, start, frames);
123 return;
124 }
125
126 auto quantif = inputs.quantification.value;
127 auto ms = inputs.ms.value;
128 for(const ossia::timed_value& v : inputs.port.value->get_data())
129 {
130 if(v.timestamp < start || v.timestamp >= start + frames)
131 continue;
132 if(quantif <= 0.)
133 {
134 if(should_output(quantif, ms, t))
135 outputs.out(v.timestamp - start, v.value);
136 }
137 else
138 {
139 for(const auto& point : t.get_quantification_dates(1. / quantif))
140 {
141 outputs.out(
142 t.physical_position(point.position, ossia_state.modelToSamples()),
143 v.value);
144 break;
145 }
146 }
147 }
148 }
149
150 void debounce(const tick& t, int64_t start, int64_t frames)
151 {
152 // Milliseconds measure model time (705600 flicks/ms), independent of the
153 // musical grid. Map through the carried sample span, including fractional
154 // flicks, so non-unit transport speeds and partial buffers stay accurate.
155 const long double begin = t.prev_date.impl;
156 const long double duration = static_cast<long double>(t.date.impl) - begin;
157 const int64_t delay = std::max(0, inputs.ms.value) * flicks_per_ms;
158 const auto emit = [&] {
159 // Never emit before the quiet interval has elapsed: round up to the first
160 // eligible sample. The tick is half-open; its end belongs to the next.
161 const auto frame
162 = std::max(0.L, std::ceil((deadline - begin) * frames / duration));
163 if(frame < frames)
164 {
165 outputs.out(static_cast<int64_t>(frame), std::move(*pending));
166 pending.reset();
167 }
168 };
169 const auto event = [&](const ossia::timed_value& v) {
170 if(v.timestamp < start || v.timestamp >= start + frames)
171 return;
172 const auto date = begin + (v.timestamp - start) * duration / frames;
173 // An arrival exactly at the deadline wins, extending the same burst.
174 // Zero delay is immediate for every event, including equal timestamps.
175 if(pending && deadline < date)
176 emit();
177 if(delay == 0)
178 {
179 pending.reset();
180 outputs.out(v.timestamp - start, v.value);
181 }
182 else
183 {
184 pending = v.value;
185 deadline = date + delay;
186 }
187 };
188
189 const auto& data = inputs.port.value->get_data();
190 if(std::is_sorted(data.begin(), data.end(), [](const auto& a, const auto& b) {
191 return a.timestamp < b.timestamp;
192 }))
193 {
194 for(const auto& v : data)
195 event(v);
196 }
197 else
198 {
199 // Fan-in may append independently sorted streams. Sort pointers, not
200 // arbitrary values, retaining arrival order for equal timestamps.
201 ordered_events.clear();
202 for(const auto& v : data)
203 ordered_events.push_back(&v);
204 std::sort(
205 ordered_events.begin(), ordered_events.end(),
206 [](const auto* a, const auto* b) {
207 return a->timestamp < b->timestamp || (a->timestamp == b->timestamp && a < b);
208 });
209 for(const auto* v : ordered_events)
210 event(*v);
211 }
212 if(pending && deadline < t.date.impl)
213 emit();
214 }
215
216 struct ui
217 {
218 halp_meta(layout, halp::layouts::hbox)
219 halp_meta(background, halp::colors::background_mid)
220 halp::control<&ins::quantification> q;
221 halp::control<&ins::ms> t;
222 halp::control<&ins::mode> mode;
223 };
224};
225}
Definition RateLimiter.hpp:42
Definition RateLimiter.hpp:217
Definition RateLimiter.hpp:22
Definition Types.hpp:49
Definition Types.hpp:40