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 
9 namespace oscr
10 {
11 
12 template <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 
37 template <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 
65 template <typename Node, typename Field>
67 {
68  using ExecNode = safe_node<Node>;
69  using Model = ProcessModel<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 };
88 template <typename Node, typename Field, std::size_t N, std::size_t NField>
90 
91 template <typename Node, typename Field, std::size_t N, std::size_t NField>
93 {
94  using ExecNode = safe_node<Node>;
95  using Model = ProcessModel<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 
168 template <typename Node, avnd::soundfile_port Field, std::size_t N, std::size_t NField>
169 struct setup_control_for_exec<Node, Field, N, NField>
170  : setup_control_for_exec_base<Node, Field>
171 {
172  using ExecNode = safe_node<Node>;
173  using Model = ProcessModel<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 
212 template <typename Node, avnd::midifile_port Field, std::size_t N, std::size_t NField>
213 struct 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 
256 template <typename Node, avnd::raw_file_port Field, std::size_t N, std::size_t NField>
257 struct 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 
335 template <typename Node>
337 {
338  using ExecNode = safe_node<Node>;
339  using Model = ProcessModel<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:171
Definition: ExecutorPortSetup.hpp:67
Definition: ExecutorPortSetup.hpp:93