Loading...
Searching...
No Matches
ExecutorPortSetup.hpp
1#pragma once
2#include <Process/Process.hpp>
3
4#include <Crousti/File.hpp>
5#include <Crousti/ProcessModel.hpp>
6
7#include <avnd/binding/ossia/node.hpp>
8
9namespace oscr
10{
11
12template <typename Node, typename Field, std::size_t NPred, std::size_t NField>
14{
15 using ExecNode = safe_node<Node>;
16 const Execution::Context& ctx;
17 std::weak_ptr<ExecNode> weak_node;
18 Field& field;
19 void operator()(const ossia::value& val)
20 {
21 using control_value_type = std::decay_t<decltype(Field::value)>;
22
23 if(auto node = weak_node.lock())
24 {
25 control_value_type v;
26 node->from_ossia_value(field, val, v, avnd::field_index<NField>{});
27 ctx.executionQueue.enqueue([weak_node = weak_node, v = std::move(v)]() mutable {
28 if(auto n = weak_node.lock())
29 {
30 n->template control_updated_from_ui<control_value_type, NPred>(std::move(v));
31 }
32 });
33 }
34 }
35};
36
37template <typename Node, typename Field, std::size_t NPred, std::size_t NField>
39{
40 using ExecNode = safe_node<Node>;
41 const Execution::Context& ctx;
42 std::weak_ptr<ExecNode> weak_node;
43 Field& field;
44 int port_index;
45 void operator()(const ossia::value& val)
46 {
47 using control_value_type = std::decay_t<decltype(Field::value)>;
48
49 if(auto node = weak_node.lock())
50 {
51 control_value_type v;
52 node->from_ossia_value(field, val, v, avnd::field_index<NField>{});
53 ctx.executionQueue.enqueue(
54 [weak_node = weak_node, port_index = port_index, v = std::move(v)]() mutable {
55 if(auto n = weak_node.lock())
56 {
57 n->template control_updated_from_ui<control_value_type, NPred>(
58 std::move(v), port_index);
59 }
60 });
61 }
62 }
63};
64
65template <typename Node, typename Field>
67{
68 using ExecNode = safe_node<Node>;
70
71 Model& element;
72 const Execution::Context& ctx;
73 const std::shared_ptr<ExecNode>& node_ptr;
74 QObject* parent;
75
76 void invoke_update(Field& param)
77 {
78 avnd::effect_container<Node>& eff = node_ptr->impl;
79 {
80 for(auto state : eff.full_state())
81 {
82 // FIXME dynamic_ports
83 if_possible(param.update(state.effect));
84 }
85 }
86 }
87};
88template <typename Node, typename Field, std::size_t N, std::size_t NField>
90
91template <typename Node, typename Field, std::size_t N, std::size_t NField>
93{
94 using ExecNode = safe_node<Node>;
96
97 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
98 {
99 // Initialize the control with the current value of the inlet if it is not an optional
100 if constexpr(avnd::dynamic_ports_port<Field>)
101 {
102 if constexpr(!requires { param.ports[0].value.reset(); })
103 {
104 this->node_ptr->from_ossia_value(
105 param, inlet->value(), param.ports[k].value, avnd::field_index<NField>{});
106 }
107 }
108 else
109 {
110 if constexpr(!requires { param.value.reset(); })
111 {
112 this->node_ptr->from_ossia_value(
113 param, inlet->value(), param.value, avnd::field_index<NField>{});
114 }
115 }
116 }
117
118 void update_controller(Field& param, Process::ControlInlet* inlet)
119 {
120 // FIXME proper tag
121 if constexpr(requires { param.update_controller; })
122 {
123 param.update_controller
124 = [inlet = QPointer{inlet}, self = QPointer{&this->element}](auto&& value) {
125 if(!self || !inlet)
126 return;
127
128 // Notify the UI if the object has the power
129 // to actually change the value of the control
130 // FIXME better to use in_edit queue ?
131 // FIXME not too efficient but which choice do we have ?
132 static thread_local const Field field;
133 ossia::qt::run_async(
134 qApp, [self, inlet, val = oscr::to_ossia_value(field, value)] {
135 if(!self || !inlet)
136 return;
137 inlet->setValue(val);
138 });
139 };
140 }
141 }
142
143 void connect_control_to_ui(Field& param, Process::ControlInlet* inlet, int k)
144 {
145 // Connect to changes
146 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
147 if constexpr(avnd::dynamic_ports_port<Field>)
148 {
149 using port_type = avnd::dynamic_port_type<Field>;
150 QObject::connect(
151 inlet, &Process::ControlInlet::valueChanged, this->parent,
153 this->ctx, weak_node, param.ports[k], k});
154
155 this->update_controller(param, inlet);
156 }
157 else
158 {
159 QObject::connect(
160 inlet, &Process::ControlInlet::valueChanged, this->parent,
161 con_unvalidated<Node, Field, N, NField>{this->ctx, weak_node, param});
162
163 this->update_controller(param, inlet);
164 }
165 }
166};
167
168template <typename Node, avnd::soundfile_port Field, std::size_t N, std::size_t NField>
169struct setup_control_for_exec<Node, Field, N, NField>
170 : setup_control_for_exec_base<Node, Field>
171{
172 using ExecNode = safe_node<Node>;
174
175 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
176 {
177 // FIXME handle dynamic ports correctly
178 // First we can load it directly since execution hasn't started yet
179 if(auto hdl = loadSoundfile(inlet->value(), this->ctx.doc, this->ctx.execState))
180 this->node_ptr->soundfile_loaded(
181 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
182 }
183
184 void connect_control_to_ui(Field& param, Process::ControlInlet* inlet, int k)
185 {
186 // Connect to changes
187 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
188 std::weak_ptr<ossia::execution_state> weak_st = this->ctx.execState;
189 QObject::connect(
190 inlet, &Process::ControlInlet::valueChanged, this->parent,
191 [&ctx = this->ctx, weak_node = std::move(weak_node),
192 weak_st = std::move(weak_st)](const ossia::value& v) {
193 if(auto n = weak_node.lock())
194 if(auto st = weak_st.lock())
195 if(auto file = loadSoundfile(v, ctx.doc, st))
196 {
197 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
198 auto n = weak_node.lock();
199 if(!n)
200 return;
201
202 // We store the sound file handle returned in this lambda so that it gets
203 // GC'd in the main thread
204 n->soundfile_loaded(
205 f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
206 });
207 }
208 });
209 }
210};
211
212template <typename Node, avnd::midifile_port Field, std::size_t N, std::size_t NField>
213struct setup_control_for_exec<Node, Field, N, NField>
214 : setup_control_for_exec_base<Node, Field>
215{
216 using ExecNode = safe_node<Node>;
217 using Model = ProcessModel<Node>;
218
219 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
220 {
221 // FIXME handle dynamic ports correctly
222
223 // First we can load it directly since execution hasn't started yet
224 if(auto hdl = loadMidifile(inlet->value(), this->ctx.doc))
225 this->node_ptr->midifile_loaded(
226 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
227 }
228
229 void connect_control_to_ui(Field& param, Process::ControlInlet* inlet, int k)
230 {
231 // Connect to changes
232 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
233 std::weak_ptr<ossia::execution_state> weak_st = this->ctx.execState;
234 QObject::connect(
235 inlet, &Process::ControlInlet::valueChanged, this->parent,
236 [inlet, &ctx = this->ctx,
237 weak_node = std::move(weak_node)](const ossia::value& v) {
238 if(auto n = weak_node.lock())
239 if(auto file = loadMidifile(v, ctx.doc))
240 {
241 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
242 auto n = weak_node.lock();
243 if(!n)
244 return;
245
246 // We store the sound file handle returned in this lambda so that it gets
247 // GC'd in the main thread
248 n->midifile_loaded(
249 f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
250 });
251 }
252 });
253 }
254};
255
256template <typename Node, avnd::raw_file_port Field, std::size_t N, std::size_t NField>
257struct setup_control_for_exec<Node, Field, N, NField>
258 : setup_control_for_exec_base<Node, Field>
259{
260 using ExecNode = safe_node<Node>;
261 using Model = ProcessModel<Node>;
262
263 static constexpr bool has_text = requires { decltype(Field::file)::text; };
264 static constexpr bool has_mmap = requires { decltype(Field::file)::mmap; };
265
266 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
267 {
268 // FIXME handle dynamic ports correctly
269
270 // First we can load it directly since execution hasn't started yet
271 if(auto hdl = loadRawfile(inlet->value(), this->ctx.doc, has_text, has_mmap))
272 {
273 if constexpr(avnd::port_can_process<Field>)
274 {
275 // FIXME also do it when we get a run-time message from the exec engine,
276 // OSC, etc
277 auto func = executePortPreprocess<Field>(*hdl);
278 this->node_ptr->file_loaded(
279 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
280 if(func)
281 func(this->node_ptr->impl.effect);
282 }
283 else
284 {
285 this->node_ptr->file_loaded(
286 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
287 }
288 }
289 }
290
291 void connect_control_to_ui(Field& param, Process::ControlInlet* inlet, int k)
292 {
293 // Connect to changes
294 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
295 std::weak_ptr<ossia::execution_state> weak_st = this->ctx.execState;
296 QObject::connect(
297 inlet, &Process::ControlInlet::valueChanged, this->parent,
298 [inlet, &ctx = this->ctx, weak_node = std::move(weak_node)] {
299 if(auto n = weak_node.lock())
300 if(auto file = loadRawfile(inlet->value(), ctx.doc, has_text, has_mmap))
301 {
302 if constexpr(avnd::port_can_process<Field>)
303 {
304 auto func = executePortPreprocess<Field>(*file);
305 ctx.executionQueue.enqueue(
306 [f = std::move(file), weak_node, ff = std::move(func)]() mutable {
307 auto n = weak_node.lock();
308 if(!n)
309 return;
310
311 // We store the sound file handle returned in this lambda so that it gets
312 // GC'd in the main thread
313 n->file_loaded(f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
314 if(ff)
315 ff(n->impl.effect);
316 });
317 }
318 else
319 {
320 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
321 auto n = weak_node.lock();
322 if(!n)
323 return;
324
325 // We store the sound file handle returned in this lambda so that it gets
326 // GC'd in the main thread
327 n->file_loaded(f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
328 });
329 }
330 }
331 });
332 }
333};
334
335template <typename Node>
337{
338 using ExecNode = safe_node<Node>;
340
341 Model& element;
342 const Execution::Context& ctx;
343 const std::shared_ptr<ExecNode>& node_ptr;
344 QObject* parent;
345
346 // Main function being invoked, which dispatches to all the actual implementations
347 template <typename Field, std::size_t N, std::size_t NField>
348 constexpr void
349 operator()(Field& param, avnd::predicate_index<N> np, avnd::field_index<NField> nf)
350 {
351 const auto ports = element.avnd_input_idx_to_model_ports(NField);
352
353 if constexpr(avnd::dynamic_ports_port<Field>)
354 {
355 param.ports.resize(ports.size());
356 }
357
358 int k = 0;
359 for(auto p : ports)
360 {
361 if(auto inlet = qobject_cast<Process::ControlInlet*>(p))
362 {
364 element, ctx, node_ptr, parent};
365
366 setup.initialize_control(param, inlet, k);
367
368 setup.invoke_update(param);
369
370 setup.connect_control_to_ui(param, inlet, k);
371 }
372 k++;
373 }
374 // Else it's an unhandled value inlet
375 }
376};
377
378}
Definition Port.hpp:203
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:77
TreeNode< DeviceExplorerNode > Node
Definition DeviceNode.hpp:74
Definition Factories.hpp:19
Definition ExecutionContext.hpp:76
ExecutionCommandQueue & executionQueue
Definition ExecutionContext.hpp:91
Definition ExecutorPortSetup.hpp:39
Definition ExecutorPortSetup.hpp:14
Definition ExecutorPortSetup.hpp:337
Definition ExecutorPortSetup.hpp:67
Definition ExecutorPortSetup.hpp:93