score-plugin-avnd/Crousti/Executor.hpp
1 #pragma once
2 
3 #include <Process/Execution/ProcessComponent.hpp>
4 #include <Process/ExecutionContext.hpp>
5 
6 #include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
7 
8 #include <Crousti/CpuAnalysisNode.hpp>
9 #include <Crousti/CpuFilterNode.hpp>
10 #include <Crousti/CpuGeneratorNode.hpp>
11 #include <Crousti/GpuComputeNode.hpp>
12 #include <Crousti/GpuNode.hpp>
13 #include <Crousti/MessageBus.hpp>
14 #include <Crousti/Metadatas.hpp>
15 #include <Crousti/ProcessModel.hpp>
16 #include <Engine/Node/TickPolicy.hpp>
17 
18 #include <score/tools/Bind.hpp>
19 
20 #include <ossia/dataflow/exec_state_facade.hpp>
21 #include <ossia/dataflow/node_process.hpp>
22 
23 #include <ossia-qt/invoke.hpp>
24 
25 #include <QGuiApplication>
26 
27 #if SCORE_PLUGIN_GFX
28 #include <Crousti/GpuNode.hpp>
29 #include <Gfx/GfxApplicationPlugin.hpp>
30 #endif
31 
32 #include <Media/AudioDecoder.hpp>
33 
34 #include <score/tools/ThreadPool.hpp>
35 
36 #include <QTimer>
37 
38 #include <avnd/binding/ossia/data_node.hpp>
39 #include <avnd/binding/ossia/mono_audio_node.hpp>
40 #include <avnd/binding/ossia/node.hpp>
41 #include <avnd/binding/ossia/ossia_audio_node.hpp>
42 #include <avnd/binding/ossia/poly_audio_node.hpp>
43 #include <avnd/concepts/temporality.hpp>
44 #include <avnd/concepts/ui.hpp>
45 #include <avnd/concepts/worker.hpp>
46 #include <libremidi/reader.hpp>
47 
48 namespace oscr
49 {
50 namespace
51 {
52 [[nodiscard]] static QString
53 filenameFromPort(const ossia::value& value, const score::DocumentContext& ctx)
54 {
55  if(auto str = value.target<std::string>())
56  return score::locateFilePath(QString::fromStdString(*str).trimmed(), ctx);
57  return {};
58 }
59 
60 // TODO refactor this into a generic explicit soundfile loaded mechanism
61 [[nodiscard]] static auto
62 loadSoundfile(const ossia::value& value, const score::DocumentContext& ctx, double rate)
63 {
64  // Initialize the control with the current soundfile
65  if(auto str = filenameFromPort(value, ctx); !str.isEmpty())
66  {
67  auto dec = Media::AudioDecoder::decode_synchronous(str, rate);
68 
69  if(dec.has_value())
70  {
71  auto hdl = std::make_shared<ossia::audio_data>();
72  hdl->data = std::move(dec->second);
73  hdl->path = str.toStdString();
74  hdl->rate = rate;
75  return hdl;
76  }
77  }
78  return ossia::audio_handle{};
79 }
80 
81 using midifile_handle = std::shared_ptr<oscr::midifile_data>;
82 [[nodiscard]] inline midifile_handle
83 loadMidifile(const ossia::value& value, const score::DocumentContext& ctx)
84 {
85  // Initialize the control with the current soundfile
86  if(auto str = filenameFromPort(value, ctx); !str.isEmpty())
87  {
88  QFile f(str);
89  if(!f.open(QIODevice::ReadOnly))
90  return {};
91  auto ptr = f.map(0, f.size());
92 
93  auto hdl = std::make_shared<oscr::midifile_data>();
94  if(auto ret = hdl->reader.parse((uint8_t*)ptr, f.size());
95  ret == libremidi::reader::invalid)
96  return {};
97 
98  hdl->filename = str.toStdString();
99  return hdl;
100  }
101  return {};
102 }
103 
104 using raw_file_handle = std::shared_ptr<raw_file_data>;
105 [[nodiscard]] inline raw_file_handle loadRawfile(
106  const ossia::value& value, const score::DocumentContext& ctx, bool text, bool mmap)
107 {
108  // Initialize the control with the current soundfile
109  if(auto filename = filenameFromPort(value, ctx); !filename.isEmpty())
110  {
111  if(!QFile::exists(filename))
112  return {};
113 
114  auto hdl = std::make_shared<oscr::raw_file_data>();
115  hdl->file.setFileName(filename);
116  if(!hdl->file.open(QIODevice::ReadOnly))
117  return {};
118 
119  if(mmap)
120  {
121  auto map = (char*)hdl->file.map(0, hdl->file.size());
122  hdl->data = QByteArray::fromRawData(map, hdl->file.size());
123  }
124  else
125  {
126  if(text)
127  hdl->file.setTextModeEnabled(true);
128 
129  hdl->data = hdl->file.readAll();
130  }
131  hdl->filename = filename.toStdString();
132  return hdl;
133  }
134  return {};
135 }
136 [[nodiscard]] inline auto loadSoundfile(
137  const ossia::value& value, const score::DocumentContext& ctx,
138  const std::shared_ptr<ossia::execution_state>& st)
139 {
140  const double rate = ossia::exec_state_facade{st.get()}.sampleRate();
141  return loadSoundfile(value, ctx, rate);
142 }
143 }
144 
145 template <typename ExecNode_T, typename T, std::size_t ControlN>
147 {
148  std::weak_ptr<ExecNode_T> weak_node;
149  T v;
150  void operator()()
151  {
152  if(auto n = weak_node.lock())
153  {
154  n->template control_updated_from_ui<T, ControlN>(std::move(v));
155  }
156  }
157 };
158 
159 template <typename Node>
161 {
162  using ExecNode = safe_node<Node>;
163  using Model = ProcessModel<Node>;
164 
165  Model& element;
166  const Execution::Context& ctx;
167  const std::shared_ptr<ExecNode>& node_ptr;
168  QObject* parent;
169 
170  template <typename Field, std::size_t NPred, std::size_t NField>
172  {
173  const Execution::Context& ctx;
174  std::weak_ptr<ExecNode> weak_node;
175  Field& field;
176  void operator()(const ossia::value& val)
177  {
178  constexpr auto control_index = NPred;
179 
180  using control_value_type = std::decay_t<decltype(Field::value)>;
181 
182  if(auto node = weak_node.lock())
183  {
184  control_value_type v;
185  node->from_ossia_value(field, val, v, avnd::field_index<NField>{});
186  ctx.executionQueue.enqueue(
188  weak_node, std::move(v)});
189  }
190  }
191  };
192 
193  template <typename Field, std::size_t N, std::size_t NField>
194  constexpr void
195  operator()(Field& param, avnd::predicate_index<N> np, avnd::field_index<NField> nf)
196  {
197  if(auto inlet
198  = dynamic_cast<Process::ControlInlet*>(modelPort<Node>(element.inlets(), NField)))
199  {
200  // Initialize the control with the current value of the inlet if it is not an optional
201  if constexpr(!requires { param.value.reset(); })
202  node_ptr->from_ossia_value(param, inlet->value(), param.value, nf);
203 
204  {
205  avnd::effect_container<Node>& eff = node_ptr->impl;
206  {
207  for(auto& state : eff.full_state())
208  {
209  if_possible(param.update(state.effect));
210  }
211  }
212  }
213 
214  // Connect to changes
215  std::weak_ptr<ExecNode> weak_node = node_ptr;
216  QObject::connect(
217  inlet, &Process::ControlInlet::valueChanged, parent,
218  con_unvalidated<Field, N, NField>{ctx, weak_node, param});
219  }
220  // Else it's an unhandled value inlet
221  }
222 
223  template <avnd::soundfile_port Field, std::size_t N, std::size_t NField>
224  void operator()(Field& param, avnd::predicate_index<N>, avnd::field_index<NField>)
225  {
226  auto inlet
227  = safe_cast<Process::ControlInlet*>(modelPort<Node>(element.inlets(), NField));
228 
229  // First we can load it directly since execution hasn't started yet
230  if(auto hdl = loadSoundfile(inlet->value(), ctx.doc, ctx.execState))
231  node_ptr->soundfile_loaded(
232  hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
233 
234  // Connect to changes
235  std::weak_ptr<ExecNode> weak_node = node_ptr;
236  std::weak_ptr<ossia::execution_state> weak_st = ctx.execState;
237  QObject::connect(
238  inlet, &Process::ControlInlet::valueChanged, parent,
239  [&ctx = this->ctx, weak_node = std::move(weak_node),
240  weak_st = std::move(weak_st)](const ossia::value& v) {
241  if(auto n = weak_node.lock())
242  if(auto st = weak_st.lock())
243  if(auto file = loadSoundfile(v, ctx.doc, st))
244  {
245  ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
246  auto n = weak_node.lock();
247  if(!n)
248  return;
249 
250  // We store the sound file handle returned in this lambda so that it gets
251  // GC'd in the main thread
252  n->soundfile_loaded(
253  f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
254  });
255  }
256  });
257  }
258 
259  template <avnd::midifile_port Field, std::size_t N, std::size_t NField>
260  void operator()(Field& param, avnd::predicate_index<N>, avnd::field_index<NField>)
261  {
262  auto inlet
263  = safe_cast<Process::ControlInlet*>(modelPort<Node>(element.inlets(), NField));
264 
265  // First we can load it directly since execution hasn't started yet
266  if(auto hdl = loadMidifile(inlet->value(), ctx.doc))
267  node_ptr->midifile_loaded(
268  hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
269 
270  // Connect to changes
271  std::weak_ptr<ExecNode> weak_node = node_ptr;
272  std::weak_ptr<ossia::execution_state> weak_st = ctx.execState;
273  QObject::connect(
274  inlet, &Process::ControlInlet::valueChanged, parent,
275  [inlet, &ctx = this->ctx,
276  weak_node = std::move(weak_node)](const ossia::value& v) {
277  if(auto n = weak_node.lock())
278  if(auto file = loadMidifile(v, ctx.doc))
279  {
280  ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
281  auto n = weak_node.lock();
282  if(!n)
283  return;
284 
285  // We store the sound file handle returned in this lambda so that it gets
286  // GC'd in the main thread
287  n->midifile_loaded(
288  f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
289  });
290  }
291  });
292  }
293 
294  template <typename Field>
295  static auto executePortPreprocess(auto& file)
296  {
297  using field_file_type = decltype(Field::file);
298  field_file_type ffile;
299  ffile.bytes = decltype(ffile.bytes)(file.data.constData(), file.file.size());
300  ffile.filename = file.filename;
301  return Field::process(ffile);
302  }
303  template <avnd::raw_file_port Field, std::size_t N, std::size_t NField>
304  void operator()(Field& param, avnd::predicate_index<N>, avnd::field_index<NField>)
305  {
306  auto inlet
307  = safe_cast<Process::ControlInlet*>(modelPort<Node>(element.inlets(), NField));
308 
309  using file_ports = avnd::raw_file_input_introspection<Node>;
310  using elt = typename file_ports::template nth_element<N>;
311  constexpr bool has_text = requires { decltype(elt::file)::text; };
312  constexpr bool has_mmap = requires { decltype(elt::file)::mmap; };
313 
314  // First we can load it directly since execution hasn't started yet
315  if(auto hdl = loadRawfile(inlet->value(), ctx.doc, has_text, has_mmap))
316  {
317  if constexpr(avnd::port_can_process<Field>)
318  {
319  // FIXME also do it when we get a run-time message from the exec engine,
320  // OSC, etc
321  auto func = executePortPreprocess<Field>(*hdl);
322  node_ptr->file_loaded(
323  hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
324  if(func)
325  func(node_ptr->impl.effect);
326  }
327  else
328  {
329  node_ptr->file_loaded(
330  hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
331  }
332  }
333 
334  // Connect to changes
335  std::weak_ptr<ExecNode> weak_node = node_ptr;
336  std::weak_ptr<ossia::execution_state> weak_st = ctx.execState;
337  QObject::connect(
338  inlet, &Process::ControlInlet::valueChanged, parent,
339  [inlet, &ctx = this->ctx, weak_node = std::move(weak_node)] {
340  if(auto n = weak_node.lock())
341  if(auto file = loadRawfile(inlet->value(), ctx.doc, has_text, has_mmap))
342  {
343  if constexpr(avnd::port_can_process<Field>)
344  {
345  auto func = executePortPreprocess<Field>(*file);
346  ctx.executionQueue.enqueue(
347  [f = std::move(file), weak_node, ff = std::move(func)]() mutable {
348  auto n = weak_node.lock();
349  if(!n)
350  return;
351 
352  // We store the sound file handle returned in this lambda so that it gets
353  // GC'd in the main thread
354  n->file_loaded(f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
355  if(ff)
356  ff(n->impl.effect);
357  });
358  }
359  else
360  {
361  ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
362  auto n = weak_node.lock();
363  if(!n)
364  return;
365 
366  // We store the sound file handle returned in this lambda so that it gets
367  // GC'd in the main thread
368  n->file_loaded(f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
369  });
370  }
371  }
372  });
373  }
374 };
375 
376 template <typename Node>
378 {
379  using ExecNode = safe_node<Node>;
380  using Model = ProcessModel<Node>;
381 
382  typename ExecNode::control_input_values_type& arr;
383  Model& element;
384 
385  template <std::size_t N, std::size_t NField>
386  void operator()(auto& field, avnd::predicate_index<N>, avnd::field_index<NField>)
387  {
388  if(auto p = modelPort<Node>(element.inlets(), NField))
389  if(auto inlet = dynamic_cast<Process::ControlInlet*>(p))
390  inlet->setExecutionValue(oscr::to_ossia_value(field, field.value));
391  }
392 };
393 
394 template <typename Node>
396 {
397  using ExecNode = safe_node<Node>;
398  using Model = ProcessModel<Node>;
399  typename ExecNode::control_output_values_type& arr;
400  Model& element;
401 
402  template <std::size_t N, std::size_t NField>
403  void operator()(auto& field, avnd::predicate_index<N>, avnd::field_index<NField>)
404  {
405  auto outlet
406  = safe_cast<Process::ControlOutlet*>(modelPort<Node>(element.outlets(), NField));
407  outlet->setValue(oscr::to_ossia_value(field, field.value));
408  }
409 };
410 
411 template <typename Node>
413 {
414  using ExecNode = safe_node<Node>;
415  using Model = ProcessModel<Node>;
416  std::weak_ptr<ExecNode> weak_node;
417  Model& element;
418 
419  void handle_controls(ExecNode& node) const noexcept
420  {
421  using namespace ossia::safe_nodes;
422  // TODO disconnect the connection ? it will be disconnected shortly
423  // after...
424 
425  typename ExecNode::control_input_values_type arr;
426  bool ok = false;
427  while(node.control.ins_queue.try_dequeue(arr))
428  {
429  ok = true;
430  }
431  if(ok)
432  {
433  for(auto& state : node.impl.full_state())
434  {
435  avnd::control_input_introspection<Node>::for_all_n2(
436  state.inputs, ApplyEngineControlChangeToUI<Node>{arr, element});
437  }
438  }
439  }
440 
441  void handle_control_outs(ExecNode& node) const noexcept
442  {
443  using namespace ossia::safe_nodes;
444  // TODO disconnect the connection ? it will be disconnected shortly
445  // after...
446  typename ExecNode::control_output_values_type arr;
447  bool ok = false;
448  while(node.control.outs_queue.try_dequeue(arr))
449  {
450  ok = true;
451  }
452  if(ok)
453  {
454  avnd::control_output_introspection<Node>::for_all_n2(
455  avnd::get_outputs<Node>(node.impl), setup_Impl1_Out<Node>{arr, element});
456  }
457  }
458 
459  void operator()() const noexcept
460  {
461  if(auto node = weak_node.lock())
462  {
463  constexpr const auto control_count = avnd::control_input_introspection<Node>::size;
464  constexpr const auto control_out_count
465  = avnd::control_output_introspection<Node>::size;
466  if constexpr(control_count > 0)
467  handle_controls(*node);
468 
469  if constexpr(control_out_count > 0)
470  handle_control_outs(*node);
471  }
472  }
473 };
474 
475 template <typename T, bool Predicate>
476 struct type_if;
477 template <typename T>
478 struct type_if<T, false>
479 {
480  type_if() = default;
481  type_if(const type_if&) = default;
482  type_if(type_if&&) = default;
483  type_if& operator=(const type_if&) = default;
484  type_if& operator=(type_if&&) = default;
485 
486  template <typename U>
487  type_if(U&&)
488  {
489  }
490  template <typename U>
491  T& operator=(U&& u) noexcept
492  {
493  return *this;
494  }
495 };
496 
497 template <typename T>
498 struct type_if<T, true>
499 {
500  [[no_unique_address]] T value;
501 
502  type_if() = default;
503  type_if(const type_if&) = default;
504  type_if(type_if&&) = default;
505  type_if& operator=(const type_if&) = default;
506  type_if& operator=(type_if&&) = default;
507 
508  template <typename U>
509  type_if(U&& other)
510  : value{std::forward<U>(other)}
511  {
512  }
513 
514  operator const T&() const noexcept { return value; }
515  operator T&() noexcept { return value; }
516  operator T&&() && noexcept { return std::move(value); }
517 
518  template <typename U>
519  T& operator=(U&& u) noexcept
520  {
521  return value = std::forward<U>(u);
522  }
523 };
524 
525 template <typename Node>
526 class CustomNodeProcess : public ossia::node_process
527 {
528  using node_process::node_process;
529  void start() override
530  {
531  node_process::start();
532  auto& n = static_cast<safe_node<Node>&>(*node);
533  n.impl.effect.start();
534  }
535  void stop() override
536  {
537  auto& n = static_cast<safe_node<Node>&>(*node);
538  n.impl.effect.stop();
539  node_process::stop();
540  }
541 };
542 
543 template <typename Node>
544 class Executor final
545  : public Execution::ProcessComponent_T<ProcessModel<Node>, ossia::node_process>
546 {
547 public:
548  static Q_DECL_RELAXED_CONSTEXPR UuidKey<score::Component> static_key() noexcept
549  {
550  return uuid_from_string<Node>();
551  }
552 
553  UuidKey<score::Component> key() const noexcept final override { return static_key(); }
554 
555  bool key_match(UuidKey<score::Component> other) const noexcept final override
556  {
557  return static_key() == other || Execution::ProcessComponent::base_key_match(other);
558  }
559 
560  [[no_unique_address]] type_if<int, is_gpu<Node>> node_id = -1;
561 
562  Executor(ProcessModel<Node>& element, const ::Execution::Context& ctx, QObject* p)
563  : Execution::ProcessComponent_T<ProcessModel<Node>, ossia::node_process>{
564  element, ctx, "Executor::ProcessModel<Info>", p}
565  {
566  const auto id
567  = std::hash<ObjectPath>{}(Path<Process::ProcessModel>{element}.unsafePath());
568 #if SCORE_PLUGIN_GFX
569  if constexpr(is_gpu<Node>)
570  {
571  auto& gfx_exec = ctx.doc.plugin<Gfx::DocumentPlugin>().exec;
572 
573  // Create the executor in the audio thread
574  auto node = std::make_shared<Gfx::gfx_exec_node>(gfx_exec);
575  node->prepare(*ctx.execState);
576 
577  this->node = node;
578 
579  // Create the controls, inputs outputs etc.
580  std::size_t i = 0;
581  for(auto& ctl : element.inlets())
582  {
583  if(auto ctrl = qobject_cast<Process::ControlInlet*>(ctl))
584  {
585  auto& p = node->add_control();
586  p->value = ctrl->value();
587  p->changed = true;
588 
589  QObject::connect(
590  ctrl, &Process::ControlInlet::valueChanged, this,
591  Gfx::con_unvalidated{ctx, i, 0, node});
592  i++;
593  }
594  else if(auto ctrl = qobject_cast<Process::ValueInlet*>(ctl))
595  {
596  auto& p = node->add_control();
597  p->changed = true;
598  i++;
599  }
600  else if(auto ctrl = qobject_cast<Process::AudioInlet*>(ctl))
601  {
602  node->add_audio();
603  }
604  else if(auto ctrl = qobject_cast<Gfx::TextureInlet*>(ctl))
605  {
606  node->add_texture();
607  }
608  }
609 
610  // FIXME refactor this with other GFX processes
611  for(auto* outlet : element.outlets())
612  {
613  if(auto ctrl = qobject_cast<Process::ControlOutlet*>(outlet))
614  {
615  node->add_control_out();
616  }
617  else if(auto ctrl = qobject_cast<Process::ValueOutlet*>(outlet))
618  {
619  node->add_control_out();
620  }
621  else if(auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
622  {
623  node->add_texture_out();
624  out->nodeId = node_id;
625  }
626  }
627 
628  // Create the GPU node
629 
630  std::weak_ptr qex_ptr = std::shared_ptr<Execution::ExecutionCommandQueue>(
631  ctx.alias.lock(), &ctx.executionQueue);
632  std::unique_ptr<score::gfx::Node> ptr;
633  if constexpr(GpuGraphicsNode2<Node>)
634  {
635  auto gpu_node = new CustomGpuNode<Node>(qex_ptr, node->control_outs, id);
636  ptr.reset(gpu_node);
637  }
638  else if constexpr(GpuComputeNode2<Node>)
639  {
640  auto gpu_node = new GpuComputeNode<Node>(qex_ptr, node->control_outs, id);
641  ptr.reset(gpu_node);
642  }
643  else if constexpr(GpuNode<Node>)
644  {
645  auto gpu_node = new GfxNode<Node>(element, qex_ptr, node->control_outs, id);
646  ptr.reset(gpu_node);
647  }
648  node->id = gfx_exec.ui->register_node(std::move(ptr));
649  node_id = node->id;
650  }
651  else
652 #endif
653  {
654  auto st = ossia::exec_state_facade{ctx.execState.get()};
655  std::shared_ptr<safe_node<Node>> ptr;
656  auto node = new safe_node<Node>{st.bufferSize(), (double)st.sampleRate(), id};
657  node->prepare(*ctx.execState.get()); // Preparation of the ossia side
658 
659  if_possible(node->impl.effect.ossia_state = st);
660  ptr.reset(node);
661  this->node = ptr;
662 
663  if constexpr(requires { ptr->impl.effect; })
664  if constexpr(std::is_same_v<std::decay_t<decltype(ptr->impl.effect)>, Node>)
665  connect_message_bus(element, ctx, ptr->impl.effect);
666  connect_worker(ctx, ptr->impl);
667 
668  node->finish_init();
669 
670  connect_controls(element, ctx, ptr);
671  update_controls(ptr);
672 
673  // To call prepare() after evertyhing is ready
674  node->audio_configuration_changed();
675  }
676 
677  if constexpr(avnd::tag_process_exec<Node>)
678  {
679  this->m_ossia_process = std::make_shared<CustomNodeProcess<Node>>(this->node);
680  }
681  else
682  {
683  this->m_ossia_process = std::make_shared<ossia::node_process>(this->node);
684  }
685  }
686 
687  void connect_controls(
688  ProcessModel<Node>& element, const ::Execution::Context& ctx,
689  std::shared_ptr<safe_node<Node>>& ptr)
690  {
691  using control_inputs_type = avnd::control_input_introspection<Node>;
692  using curve_inputs_type = avnd::curve_input_introspection<Node>;
693  using soundfile_inputs_type = avnd::soundfile_input_introspection<Node>;
694  using midifile_inputs_type = avnd::midifile_input_introspection<Node>;
695  using raw_file_inputs_type = avnd::raw_file_input_introspection<Node>;
696  using control_outputs_type = avnd::control_output_introspection<Node>;
697 
698  // UI controls to engine
699  safe_node<Node>& node = *ptr;
700  avnd::effect_container<Node>& eff = node.impl;
701 
702  if constexpr(control_inputs_type::size > 0)
703  {
704  // Initialize all the controls in the node with the current value.
705  // And update the node when the UI changes
706 
707  for(auto& state : eff.full_state())
708  {
709  control_inputs_type::for_all_n2(
710  state.inputs, setup_Impl0<Node>{element, ctx, ptr, this});
711  }
712  }
713  if constexpr(curve_inputs_type::size > 0)
714  {
715  // Initialize all the controls in the node with the current value.
716  // And update the node when the UI changes
717 
718  for(auto& state : eff.full_state())
719  {
720  curve_inputs_type::for_all_n2(
721  state.inputs, setup_Impl0<Node>{element, ctx, ptr, this});
722  }
723  }
724  if constexpr(soundfile_inputs_type::size > 0)
725  {
726  soundfile_inputs_type::for_all_n2(
727  avnd::get_inputs<Node>(eff), setup_Impl0<Node>{element, ctx, ptr, this});
728 
729  auto& tq = score::TaskPool::instance();
730  node.soundfiles.load_request
731  = [&tq, p = std::weak_ptr{ptr}, &ctx](std::string& str, int idx) {
732  auto eff_ptr = p.lock();
733  if(!eff_ptr)
734  return;
735  tq.post([eff_ptr = std::move(eff_ptr), filename = str, &ctx, idx]() mutable {
736  if(auto file = loadSoundfile(filename, ctx.doc, ctx.execState))
737  {
738  ctx.executionQueue.enqueue(
739  [sf = std::move(file), p = std::weak_ptr{eff_ptr}, idx]() mutable {
740  auto eff_ptr = p.lock();
741  if(!eff_ptr)
742  return;
743 
744  avnd::effect_container<Node>& eff = eff_ptr->impl;
745  soundfile_inputs_type::for_nth_mapped_n2(
746  avnd::get_inputs<Node>(eff), idx,
747  [&]<std::size_t NField, std::size_t N>(
748  auto& field, avnd::predicate_index<N> p,
749  avnd::field_index<NField> f) {
750  eff_ptr->soundfile_loaded(sf, p, f);
751  });
752  });
753  }
754  });
755  };
756  }
757  if constexpr(midifile_inputs_type::size > 0)
758  {
759  midifile_inputs_type::for_all_n2(
760  avnd::get_inputs<Node>(eff), setup_Impl0<Node>{element, ctx, ptr, this});
761  }
762  if constexpr(raw_file_inputs_type::size > 0)
763  {
764  raw_file_inputs_type::for_all_n2(
765  avnd::get_inputs<Node>(eff), setup_Impl0<Node>{element, ctx, ptr, this});
766  }
767 
768  // Engine to ui controls
769  if constexpr(control_inputs_type::size > 0 || control_outputs_type::size > 0)
770  {
771  // Update the value in the UI
772  std::weak_ptr<safe_node<Node>> weak_node = ptr;
773  ExecutorGuiUpdate<Node> timer_action{weak_node, element};
774  timer_action();
775 
776  con(
777  ctx.doc.coarseUpdateTimer, &QTimer::timeout, this, [=] { timer_action(); },
778  Qt::QueuedConnection);
779  }
780  }
781 
782  void connect_message_bus(
783  ProcessModel<Node>& element, const ::Execution::Context& ctx, Node& eff)
784  {
785  // Custom UI messages to engine
786  if constexpr(avnd::has_gui_to_processor_bus<Node>)
787  {
788  element.from_ui = [p = QPointer{this}, &eff](QByteArray b) {
789  if(!p)
790  return;
791 
792  p->in_exec([mess = std::move(b), &eff]() mutable {
793  using refl = avnd::function_reflection<&Node::process_message>;
794  static_assert(refl::count <= 1);
795 
796  if constexpr(refl::count == 0)
797  {
798  // no arguments, just call it
799  eff.process_message();
800  }
801  else if constexpr(refl::count == 1)
802  {
803  using arg_type = avnd::first_argument<&Node::process_message>;
804  std::decay_t<arg_type> arg;
805  MessageBusReader reader{mess};
806  reader(arg);
807  eff.process_message(std::move(arg));
808  }
809  });
810  };
811  }
812 
813  if constexpr(avnd::has_processor_to_gui_bus<Node>)
814  {
815  eff.send_message = [this](auto b) mutable {
816  this->in_edit([this, bb = std::move(b)]() mutable {
817  if(this->process().to_ui)
818  MessageBusSender{this->process().to_ui}(std::move(bb));
819  });
820  };
821  }
822  }
823 
824  void connect_worker(const ::Execution::Context& ctx, avnd::effect_container<Node>& eff)
825  {
826  if constexpr(avnd::has_worker<Node>)
827  {
828  // Initialize the thread pool beforehand
829  auto& tq = score::TaskPool::instance();
830  using worker_type = decltype(eff.effect.worker);
831  for(auto& eff : eff.effects())
832  {
833  std::weak_ptr eff_ptr = std::shared_ptr<Node>(this->node, &eff);
834  std::weak_ptr qex_ptr = std::shared_ptr<Execution::ExecutionCommandQueue>(
835  ctx.alias.lock(), &ctx.executionQueue);
836 
837  eff.worker.request
838  = [&tq, qex_ptr = std::move(qex_ptr),
839  eff_ptr = std::move(eff_ptr)]<typename... Args>(Args&&... f) {
840  // request() is invoked in the DSP / processor thread
841  // and just posts the task to the thread pool
842  tq.post([eff_ptr = std::move(eff_ptr), qex_ptr = std::move(qex_ptr),
843  ... ff = std::forward<Args>(f)]() mutable {
844  // This happens in the worker thread
845  // If for some reason the object has already been removed, not much
846  // reason to perform the work
847  if(!eff_ptr.lock())
848  return;
849 
850  using type_of_result
851  = decltype(worker_type::work(std::forward<decltype(ff)>(ff)...));
852  if constexpr(std::is_void_v<type_of_result>)
853  {
854  worker_type::work(std::forward<decltype(ff)>(ff)...);
855  }
856  else
857  {
858  // If the worker returns a std::function, it
859  // is to be invoked back in the processor DSP thread
860  auto res = worker_type::work(std::forward<decltype(ff)>(ff)...);
861  if(!res)
862  return;
863 
864  // Execution queue is currently spsc from main thread to an exec thread,
865  // we cannot just yeet the result back from the thread-pool
866  ossia::qt::run_async(
867  qApp, [eff_ptr = std::move(eff_ptr), qex_ptr = std::move(qex_ptr),
868  res = std::move(res)]() mutable {
869  // Main thread
870  std::shared_ptr qex = qex_ptr.lock();
871  if(!qex)
872  return;
873 
874  qex->enqueue(
875  [eff_ptr = std::move(eff_ptr), res = std::move(res)]() mutable {
876  // DSP / processor thread
877  // We need res to be mutable so that the worker can use it to e.g. store
878  // old data which will be freed back in the main thread
879  if(auto p = eff_ptr.lock())
880  res(*p);
881  });
882  });
883  }
884  });
885  };
886  }
887  }
888  }
889 
890  // Update everything
891  void update_controls(std::shared_ptr<safe_node<Node>>& ptr)
892  {
893  avnd::effect_container<Node>& eff = ptr->impl;
894  {
895  for(auto& state : eff.full_state())
896  {
897  avnd::input_introspection<Node>::for_all(
898  state.inputs, [&](auto& field) { if_possible(field.update(state.effect)); });
899  }
900  }
901  }
902 
903  void cleanup() override
904  {
905  if constexpr(requires { this->process().from_ui; })
906  {
907  this->process().from_ui = [](QByteArray arr) {};
908  }
909  // FIXME cleanup eff.effect.send_message too ?
910 
911 #if SCORE_PLUGIN_GFX
912  if constexpr(is_gpu<Node>)
913  {
914  // FIXME this must move in the Node dtor. See video_node
915  auto& gfx_exec = this->system().doc.template plugin<Gfx::DocumentPlugin>().exec;
916  if(node_id >= 0)
917  gfx_exec.ui->unregister_node(node_id);
918  }
919 
920  // FIXME refactor this with other GFX processes
921  for(auto* outlet : this->process().outlets())
922  {
923  if(auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
924  {
925  out->nodeId = -1;
926  }
927  }
928 #endif
929  ::Execution::ProcessComponent::cleanup();
930  }
931 
932  ~Executor() { }
933 };
934 }
Definition: GfxApplicationPlugin.hpp:13
Definition: score-lib-process/Process/Dataflow/Port.hpp:202
Definition: UuidKey.hpp:343
Definition: score-plugin-avnd/Crousti/Executor.hpp:527
Definition: score-plugin-avnd/Crousti/Executor.hpp:546
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:551
QString locateFilePath(const QString &filename, const score::DocumentContext &ctx) noexcept
Definition: File.cpp:57
Definition: ExecutionContext.hpp:75
ExecutionCommandQueue & executionQueue
Definition: ExecutionContext.hpp:90
Definition: Process/Execution/ProcessComponent.hpp:89
Definition: GfxExecNode.hpp:113
Definition: score-plugin-avnd/Crousti/Executor.hpp:378
Definition: score-plugin-avnd/Crousti/Executor.hpp:413
Definition: MessageBus.hpp:120
Definition: MessageBus.hpp:37
Definition: score-plugin-avnd/Crousti/Executor.hpp:147
Definition: score-plugin-avnd/Crousti/Executor.hpp:172
Definition: score-plugin-avnd/Crousti/Executor.hpp:161
Definition: score-plugin-avnd/Crousti/Executor.hpp:396
Definition: score-plugin-avnd/Crousti/Executor.hpp:476
Definition: DocumentContext.hpp:18
Definition: ObjectPath.hpp:186