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