Loading...
Searching...
No Matches
MathMapping_generic.hpp
1#pragma once
2#include <Fx/MathHelpers.hpp>
3#include <Fx/Types.hpp>
4
5#include <ossia/dataflow/value_port.hpp>
6
7#include <halp/callback.hpp>
8namespace Nodes
9{
10template <typename State>
12{
16 static void store_output_vec(auto& self, const auto& vec)
17 {
18 const auto n = std::min(vec.size(), self.pov.size());
19 for(std::size_t i = 0; i < n; i++)
20 self.pov[i] = vec[i];
21 }
22
23 static void store_output(auto& self, const ossia::value& v)
24 {
25 switch(v.get_type())
26 {
27 case ossia::val_type::NONE:
28 break;
29 case ossia::val_type::FLOAT:
30 self.po = *v.target<float>();
31 break;
32 case ossia::val_type::VEC2F: {
33 if constexpr(requires { self.pov; })
34 store_output_vec(self, *v.target<ossia::vec2f>());
35 break;
36 }
37 case ossia::val_type::VEC3F: {
38 if constexpr(requires { self.pov; })
39 store_output_vec(self, *v.target<ossia::vec3f>());
40 break;
41 }
42 case ossia::val_type::VEC4F: {
43 if constexpr(requires { self.pov; })
44 store_output_vec(self, *v.target<ossia::vec4f>());
45 break;
46 }
47 case ossia::val_type::LIST: {
48 if constexpr(requires { self.pov; })
49 {
50 auto& arr = *v.target<std::vector<ossia::value>>();
51 // pov is an exprtk vector *view* onto this buffer: it must keep both
52 // its size and its data pointer, so write in place and drop anything
53 // that does not fit rather than growing (and reallocating) it.
54 const auto n = std::min(arr.size(), self.pov.size());
55 for(std::size_t i = 0; i < n; i++)
56 self.pov[i] = ossia::convert<float>(arr[i]);
57 }
58 break;
59 }
60 // Only these types are used now as per ossia::math_expression::result()
61 default:
62 break;
63 }
64 }
65
66 static void exec_scalar(State& self, value_output_callback& output)
67 {
68 auto res = self.expr.result();
69
70 self.px = self.x;
71 if(!res.valid())
72 return;
73
74 store_output(self, res);
75 output(res);
76 }
77
81 static bool exec_polyphonic(State& self, std::vector<ossia::value>& res)
82 {
83 const auto N = self.expressions.size();
84 res.resize(N);
85
86 for(std::size_t i = 0; i < N; i++)
87 {
88 auto& e = self.expressions[i];
89 auto r = e.expr.result();
90 if(!r.valid())
91 return false;
92
93 if constexpr(requires { e.x; })
94 e.px = e.x;
95
96 // Feeds `po` for the next cycle...
97 store_output(e, r);
98 // ... but the output is the expression's actual result, which may well be
99 // a point rather than a scalar (`return [x, y]`).
100 res[i] = std::move(r);
101 }
102 return true;
103 }
104
105 static void
106 exec_array(State& self, value_output_callback& output, bool vector_size_did_change)
107 {
108 if(self.xv.empty())
109 return;
110
111 if(vector_size_did_change)
112 {
113 // xv and pxv are exprtk views: re-point both, then recompile so that the
114 // new sizes are baked in. This can legitimately fail - `xv[2]` cannot
115 // compile against a one-element input - in which case we simply produce
116 // nothing until an input of a workable size arrives.
117 self.pxv.resize(self.xv.size());
118 self.expr.rebase_vector("xv", self.xv);
119 self.expr.rebase_vector("pxv", self.pxv);
120 self.expr.recompile();
121 }
122
123 if(!self.expr.valid())
124 return;
125
126 auto res = self.expr.result();
127 store_output(self, res);
128
129 // Save the previous input
130 self.pxv.assign(self.xv.begin(), self.xv.end());
131
132 if(res.valid())
133 output(std::move(res));
134 }
135
136 static void run_scalar(
137 const ossia::value& v, value_output_callback& output, const halp::tick_flicks& tk,
138 State& self)
139 {
140 setMathExpressionTiming(
141 self, tk.start_in_flicks, self.last_value_time, tk.relative_position);
142 self.last_value_time = tk.start_in_flicks;
143
144 switch(v.get_type())
145 {
146 case ossia::val_type::NONE:
147 break;
148 case ossia::val_type::IMPULSE:
149 break;
150 case ossia::val_type::INT:
151 self.x = *v.target<int>();
152 break;
153 case ossia::val_type::FLOAT:
154 self.x = *v.target<float>();
155 break;
156 case ossia::val_type::BOOL:
157 self.x = *v.target<bool>() ? 1.f : 0.f;
158 break;
159 case ossia::val_type::STRING:
160 self.x = ossia::convert<float>(v);
161 break;
162 case ossia::val_type::VEC2F:
163 self.x = (*v.target<ossia::vec2f>())[0];
164 break;
165 case ossia::val_type::VEC3F:
166 self.x = (*v.target<ossia::vec3f>())[0];
167 break;
168 case ossia::val_type::VEC4F:
169 self.x = (*v.target<ossia::vec4f>())[0];
170 break;
171 case ossia::val_type::LIST: {
172 auto& arr = *v.target<std::vector<ossia::value>>();
173 if(!arr.empty())
174 self.x = ossia::convert<float>(arr[0]);
175 break;
176 }
177 case ossia::val_type::MAP: {
178 auto& arr = *v.target<ossia::value_map_type>();
179 if(!arr.empty())
180 self.x = ossia::convert<float>(arr.begin()->second);
181 break;
182 }
183 }
184
185 GenericMathMapping::exec_scalar(self, output);
186 }
187
188 static bool resize(const std::string& expr, State& self, int sz)
189 {
190 if(std::ssize(self.expressions) == sz && expr == self.last_expression)
191 return self.last_expression_ok;
192
193 self.expressions.resize(sz);
194 self.count = sz;
195
196 bool ok = true;
197 int i = 0;
198 for(auto& e : self.expressions)
199 {
200 // init() adds the symbols and registers the symbol table; doing it again
201 // on an expression that already has them would stack up duplicate symbol
202 // tables every time the text changes.
203 if(!std::exchange(e.initialized, true))
204 e.init(self.cur_time, self.cur_deltatime, self.cur_pos, self.count);
205
206 e.instance = i;
207 if(!e.expr.set_expression(expr))
208 ok = false;
209 e.expr.seed_random(
210 UINT64_C(0xda3e39cb94b95bdb), UINT64_C(0x853c49e6748fea9b) * (1 + i));
211 i++;
212 }
213
214 // Remember the outcome, successful or not: an expression that does not
215 // compile must not be re-parsed for all 1024 elements on every tick.
216 self.last_expression = expr;
217 self.last_expression_ok = ok;
218 return ok;
219 }
220
221 static void run_polyphonic(
222 int size, value_output_callback& output, const std::string& expr,
223 const halp::tick_flicks& tk, State& self)
224 {
225 // `po` is the element's own previous output: an expression that reads it
226 // needs one instance - with its own state - per element. Everything else
227 // can go through a single expression re-evaluated for each index, which
228 // also lets each element return a vector (`return [x, y]`).
229 if(size <= 1 || uses_identifier(expr, "po"))
230 {
231 size = std::clamp(size, 0, 1024);
232 if(!resize(expr, self, size))
233 return;
234
235 setMathExpressionTiming(
236 self, tk.start_in_flicks, self.last_value_time, tk.relative_position);
237 self.last_value_time = tk.start_in_flicks;
238
239 std::vector<ossia::value> res;
241 output(std::move(res));
242 }
243 else
244 {
245 if(!resize(expr, self, 1))
246 return;
247
248 setMathExpressionTiming(
249 self, tk.start_in_flicks, self.last_value_time, tk.relative_position);
250 self.last_value_time = tk.start_in_flicks;
251
252 std::vector<ossia::value> res;
253 res.resize(size);
254 self.count = size;
255 for(int i = 0; i < size; i++)
256 {
257 auto& e = self.expressions[0];
258 e.instance = i;
259 res[i] = e.expr.result();
260 if(!res[i].valid())
261 return;
262
263 // po isn't used either store_output(e, res);
264 }
265 output(std::move(res));
266 }
267 }
268
269 static void run_polyphonic(
270 const ossia::value& value, value_output_callback& output, const std::string& expr,
271 const halp::tick_flicks& tk, State& self)
272 {
273 setMathExpressionTiming(
274 self, tk.start_in_flicks, self.last_value_time, tk.relative_position);
275 self.last_value_time = tk.start_in_flicks;
276 //auto ratio = st.modelToSamples();
277 //auto parent_dur = tk.parent_duration.impl * ratio;
278 //for(const ossia::timed_value& v : input.get_data())
279 //{
280 // auto val = value.target<std::vector<ossia::value>>();
281 // if(!val)
282 // return;
283
284 // int64_t new_time = tk.prev_date.impl * ratio + timestamp;
285 // setMathExpressionTiming(self, new_time, self.last_value_time, parent_dur);
286 // self.last_value_time = new_time;
287
288 switch(value.get_type())
289 {
290 case ossia::val_type::NONE:
291 break;
292 case ossia::val_type::IMPULSE:
293 break;
294 case ossia::val_type::INT:
295 if(!resize(expr, self, 1))
296 return;
297 self.expressions[0].x = *value.target<int>();
298 break;
299 case ossia::val_type::FLOAT:
300 if(!resize(expr, self, 1))
301 return;
302 self.expressions[0].x = *value.target<float>();
303 break;
304 case ossia::val_type::BOOL:
305 if(!resize(expr, self, 1))
306 return;
307 self.expressions[0].x = *value.target<bool>() ? 1.f : 0.f;
308 break;
309 case ossia::val_type::STRING:
310 if(!resize(expr, self, 1))
311 return;
312 self.expressions[0].x = ossia::convert<float>(value);
313 break;
314 case ossia::val_type::VEC2F:
315 if(!resize(expr, self, 2))
316 return;
317 self.expressions[0].x = (*value.target<ossia::vec2f>())[0];
318 self.expressions[1].x = (*value.target<ossia::vec2f>())[1];
319 break;
320 case ossia::val_type::VEC3F:
321 if(!resize(expr, self, 3))
322 return;
323 self.expressions[0].x = (*value.target<ossia::vec3f>())[0];
324 self.expressions[1].x = (*value.target<ossia::vec3f>())[1];
325 self.expressions[2].x = (*value.target<ossia::vec3f>())[2];
326 break;
327 case ossia::val_type::VEC4F:
328 if(!resize(expr, self, 4))
329 return;
330 self.expressions[0].x = (*value.target<ossia::vec4f>())[0];
331 self.expressions[1].x = (*value.target<ossia::vec4f>())[1];
332 self.expressions[2].x = (*value.target<ossia::vec4f>())[2];
333 self.expressions[3].x = (*value.target<ossia::vec4f>())[3];
334 break;
335 case ossia::val_type::LIST: {
336 auto& arr = *value.target<std::vector<ossia::value>>();
337 const auto N = std::clamp((int)std::ssize(arr), 0, 1024);
338 if(!resize(expr, self, N))
339 return;
340 for(int i = 0; i < N; i++)
341 self.expressions[i].x = ossia::convert<float>(arr[i]);
342 break;
343 }
344 case ossia::val_type::MAP: {
345 auto& arr = *value.target<ossia::value_map_type>();
346 const auto N = std::clamp((int)std::ssize(arr), 0, 1024);
347 if(!resize(expr, self, N))
348 return;
349 int i = 0;
350 for(auto& [k, v] : arr)
351 self.expressions[i++].x = ossia::convert<float>(v);
352 break;
353 }
354 }
355
356 std::vector<ossia::value> res;
358 output(std::move(res));
359 }
360
361 static void run_array(
362 const ossia::value& value, value_output_callback& output,
363 const halp::tick_flicks& tk, State& self)
364 {
365 //auto ratio = st.modelToSamples();
366 //auto parent_dur = tk.parent_duration.impl * ratio;
367 //for(const ossia::timed_value& v : input.get_data())
368 //{
369 // int64_t new_time = tk.prev_date.impl * ratio + timestamp;
370 // setMathExpressionTiming(self, new_time, self.last_value_time, parent_dur);
371 // self.last_value_time = new_time;
372
373 auto array_run_scalar = [&](float in) {
374 auto old_size = self.xv.size();
375 self.xv.assign(1, in);
376 auto new_size = 1U;
377 GenericMathMapping::exec_array(self, output, old_size != new_size);
378 };
379
380 switch(value.get_type())
381 {
382 case ossia::val_type::NONE:
383 break;
384 case ossia::val_type::IMPULSE:
385 GenericMathMapping::exec_array(self, output, false);
386 break;
387 case ossia::val_type::INT:
388 array_run_scalar(*value.target<int>());
389 break;
390 case ossia::val_type::FLOAT:
391 array_run_scalar(*value.target<float>());
392 break;
393 case ossia::val_type::BOOL:
394 array_run_scalar(*value.target<bool>() ? 1.f : 0.f);
395 break;
396 case ossia::val_type::STRING:
397 array_run_scalar(ossia::convert<float>(value));
398 break;
399 case ossia::val_type::VEC2F: {
400 auto& arr = *value.target<ossia::vec2f>();
401 auto old_size = self.xv.size();
402 self.xv.assign(arr.begin(), arr.end());
403 auto new_size = 2U;
404 GenericMathMapping::exec_array(self, output, old_size != new_size);
405 break;
406 }
407 case ossia::val_type::VEC3F: {
408 auto& arr = *value.target<ossia::vec3f>();
409 auto old_size = self.xv.size();
410 self.xv.assign(arr.begin(), arr.end());
411 auto new_size = 3U;
412 GenericMathMapping::exec_array(self, output, old_size != new_size);
413 break;
414 }
415 case ossia::val_type::VEC4F: {
416 auto& arr = *value.target<ossia::vec4f>();
417 auto old_size = self.xv.size();
418 self.xv.assign(arr.begin(), arr.end());
419 auto new_size = 4U;
420 GenericMathMapping::exec_array(self, output, old_size != new_size);
421 break;
422 }
423 case ossia::val_type::LIST: {
424 auto& arr = *value.target<std::vector<ossia::value>>();
425 auto old_size = self.xv.size();
426 self.xv.resize(arr.size());
427 auto new_size = arr.size();
428 for(std::size_t i = 0; i < arr.size(); i++)
429 {
430 self.xv[i] = ossia::convert<float>(arr[i]);
431 }
432 GenericMathMapping::exec_array(self, output, old_size != new_size);
433 break;
434 }
435 case ossia::val_type::MAP: {
436 auto& arr = *value.target<ossia::value_map_type>();
437 auto old_size = self.xv.size();
438 self.xv.resize(arr.size());
439 auto new_size = arr.size();
440 int i = 0;
441 for(const auto& [k, v] : arr)
442 {
443 self.xv[i++] = ossia::convert<float>(v);
444 }
445 GenericMathMapping::exec_array(self, output, old_size != new_size);
446 break;
447 }
448 }
449 //}
450 }
451};
452}
Utilities for OSSIA data structures.
Definition DeviceInterface.hpp:35
Definition MathMapping_generic.hpp:12
static void store_output_vec(auto &self, const auto &vec)
Definition MathMapping_generic.hpp:16
static bool exec_polyphonic(State &self, std::vector< ossia::value > &res)
Definition MathMapping_generic.hpp:81