120 return uuid_from_string<Node>();
127 return static_key() == other || Execution::ProcessComponent::base_key_match(other);
130#if defined(SCORE_PLUGIN_GFX)
131 [[no_unique_address]] ossia::type_if<int, is_gpu<Node>> node_id = score::gfx::invalid_node_index;
136 element, ctx,
"Executor::ProcessModel<Info>", p}
141 setup_gpu(element, ctx, p);
146 setup_cpu(element, ctx, p);
149 if constexpr(avnd::tag_process_exec<Node>)
151 this->m_ossia_process = std::make_shared<CustomNodeProcess<Node>>(this->node);
155 this->m_ossia_process = std::make_shared<ossia::node_process>(this->node);
163 = *ctx.doc.findPlugin<Explorer::DeviceDocumentPlugin>()->networkContext();
167 auto st = ossia::exec_state_facade{ctx.execState.get()};
168 std::shared_ptr<safe_node<Node>> ptr;
169 auto node =
new safe_node<Node>{st.bufferSize(), (double)st.sampleRate(),
id};
170 node->root_inputs().reserve(element.inlets().size());
171 node->root_outputs().reserve(element.outlets().size());
173 node->prepare(*ctx.execState.get());
175 if_possible(node->impl.effect.ossia_state = st);
176 if_possible(node->impl.effect.io_context = &net_ctx.context);
177 if_possible(node->impl.effect.ossia_document_context = &ctx.doc);
181 if constexpr(
requires { ptr->impl.effect; })
182 if constexpr(std::is_same_v<std::decay_t<
decltype(ptr->impl.effect)>, Node>)
184 connect_message_bus(element, ctx, ptr->impl.effect);
185 connect_dynamic_items(element, ptr->impl.effect);
187 connect_worker(ctx, ptr->impl);
189 node->dynamic_ports = element.dynamic_ports;
192 connect_controls(element, ctx, ptr);
193 update_controls(ptr);
196 node->audio_configuration_changed(st);
198 if constexpr(oscr::has_dynamic_ports<Node>)
201 &element, &Process::ProcessModel::inletsChanged,
this,
204 &element, &Process::ProcessModel::outletsChanged,
this,
207 this->m_oldInlets = element.inlets();
208 this->m_oldOutlets = element.outlets();
225 using Gfx::gfx_exec_node::gfx_exec_node;
226 std::string label()
const noexcept override
228 return std::string(avnd::get_name<Node>());
232 auto node = std::make_shared<named_exec_node>(gfx_exec);
233 node->prepare(*ctx.execState);
240 for(
auto& ctl : element.inlets())
242 if(
auto ctrl = qobject_cast<Process::ControlInlet*>(ctl))
244 auto& p = node->add_control();
245 p->value = ctrl->value();
249 ctrl, &Process::ControlInlet::valueChanged,
this,
253 else if(
auto ctrl = qobject_cast<Process::ValueInlet*>(ctl))
255 auto& p = node->add_control();
259 else if(
auto ctrl = qobject_cast<Process::AudioInlet*>(ctl))
263 else if(
auto ctrl = qobject_cast<Gfx::TextureInlet*>(ctl))
265 ossia::texture_inlet& inl = *node->add_texture();
266 ctrl->setupExecution(inl,
this);
268 else if(
auto ctrl = qobject_cast<Gfx::GeometryInlet*>(ctl))
270 ossia::geometry_inlet& inl = *node->add_geometry();
271 ctrl->setupExecution(inl,
this);
276 for(
auto* outlet : element.outlets())
278 if(
auto ctrl = qobject_cast<Process::ControlOutlet*>(outlet))
280 node->add_control_out();
282 else if(
auto ctrl = qobject_cast<Process::ValueOutlet*>(outlet))
284 node->add_control_out();
286 else if(
auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
288 node->add_texture_out();
289 out->nodeId = node_id;
291 else if(
auto out = qobject_cast<Gfx::GeometryOutlet*>(outlet))
293 node->add_geometry_out();
298 std::weak_ptr qex_ptr = std::shared_ptr<Execution::ExecutionCommandQueue>(
299 ctx.alias.lock(), &ctx.executionQueue);
300 std::unique_ptr<score::gfx::Node> ptr;
303 auto gpu_node =
new CustomGpuNode<Node>(qex_ptr, node->control_outs,
id, ctx.doc);
308 auto gpu_node =
new GpuComputeNode<Node>(qex_ptr, node->control_outs,
id, ctx.doc);
314 =
new GfxNode<Node>(element, qex_ptr, node->control_outs,
id, ctx.doc);
319 for(
auto& ctl : element.inlets())
321 if(
auto ctrl = qobject_cast<Gfx::TextureInlet*>(ctl))
323 ossia::texture_inlet& inl
324 =
static_cast<ossia::texture_inlet&
>(*node->root_inputs()[i]);
325 ptr->process(i, inl.data);
329 node->id = gfx_exec.ui->register_node(std::move(ptr));
348 if constexpr(oscr::has_dynamic_ports<Node> && !
is_gpu<Node>)
351 auto n = std::dynamic_pointer_cast<safe_node<Node>>(this->node);
355 auto& element = this->process();
367 for(
auto port : old_inlets)
368 if(!ossia::contains(new_inlets, port))
369 this->m_connectedControls.erase(port);
375 ossia::small_vector<Process::Cable*, 8> cables;
376 auto collect_cables = [&](
const auto& ports) {
377 for(
auto port : ports)
378 for(
auto& cbl : port->cables())
379 if(
auto c = cbl.try_find(ctx.doc))
380 if(setup.m_cables.find(c->id()) != setup.m_cables.end())
381 if(!ossia::contains(cables, c))
384 collect_cables(old_inlets);
385 collect_cables(old_outlets);
386 collect_cables(new_inlets);
387 collect_cables(new_outlets);
392 ossia::inlets new_inls_buffer;
393 ossia::outlets new_outls_buffer;
394 inlet_reload_storage<T> reload_inlet;
395 outlet_reload_storage<T> reload_outlet;
396 port_storage(std::size_t ins, std::size_t outs)
398 new_inls_buffer.reserve(ins);
399 new_outls_buffer.reserve(outs);
402 auto port_st = std::make_shared<port_storage>(new_inlets.size(), new_outlets.size());
403 auto& inbuf = port_st->new_inls_buffer;
404 auto& outbuf = port_st->new_outls_buffer;
409 n->audio_ports.init(inbuf, outbuf);
410 n->arg_value_ports.init(inbuf, outbuf);
411 n->message_ports.init(inbuf);
413 auto& dp = element.dynamic_ports;
414 if constexpr(avnd::inputs_type<T>::size > 0)
416 using in_info = avnd::input_introspection<T>;
417 using in_type =
typename avnd::inputs_type<T>::type;
418 auto& port_tuple = n->ossia_inlets.ports;
420 [&]<
typename K, K... Index>(std::integer_sequence<K, Index...>) {
421 reload_inlets<safe_node_base_base<T>> init{*n, inbuf, dp};
423 avnd::field_reflection<Index, avnd::pfr::tuple_element_t<Index, in_type>>{},
424 tuplet::get<Index>(port_tuple),
425 tuplet::get<Index>(port_st->reload_inlet.ports)),
427 }(
typename in_info::indices_n{});
430 if constexpr(avnd::outputs_type<T>::size > 0)
432 using out_info = avnd::output_introspection<T>;
433 using out_type =
typename avnd::outputs_type<T>::type;
434 auto& port_tuple = n->ossia_outlets.ports;
436 [&]<
typename K, K... Index>(std::integer_sequence<K, Index...>) {
437 reload_outlets<safe_node_base_base<T>> init{*n, outbuf, dp};
439 avnd::field_reflection<
440 Index, avnd::pfr::tuple_element_t<Index, out_type>>{},
441 tuplet::get<Index>(port_tuple),
442 tuplet::get<Index>(port_st->reload_outlet.ports)),
444 }(
typename out_info::indices_n{});
449 SCORE_SOFT_ASSERT(inbuf.size() == new_inlets.size());
450 SCORE_SOFT_ASSERT(outbuf.size() == new_outlets.size());
456 setup.removeCable(*c, commands);
459 setup.unregister_node_soft(old_inlets, old_outlets, this->node, commands);
463 [node = n, dp, port_st, gcq = ctx.weakGCQueue(),
464 wg = std::weak_ptr{ctx.execGraph}]()
mutable {
465 OSSIA_ENSURE_CURRENT_THREAD_KIND(ossia::thread_type::Audio);
471 if(
auto g = wg.lock())
473 const auto& inbuf = port_st->new_inls_buffer;
474 const auto& outbuf = port_st->new_outls_buffer;
475 for(
auto* p : node->root_inputs())
477 if(ossia::contains(inbuf, p))
479 const auto edges = p->sources;
483 for(
auto* p : node->root_outputs())
485 if(ossia::contains(outbuf, p))
487 const auto edges = p->targets;
493 node->dynamic_ports = dp;
494 node->reload_all_ports(port_st->reload_inlet, port_st->reload_outlet);
496 auto& inbuf = port_st->new_inls_buffer;
497 auto& outbuf = port_st->new_outls_buffer;
498 node->root_inputs().assign(inbuf.begin(), inbuf.end());
499 node->root_outputs().assign(outbuf.begin(), outbuf.end());
504 if constexpr(avnd::dynamic_ports_input_introspection<T>::size > 0)
506 for(
auto state : node->impl.full_state())
508 avnd::dynamic_ports_input_introspection<T>::for_all_n2(
509 state.inputs, [&]<std::size_t Idx>(
510 auto& field,
auto, avnd::field_index<Idx> idx) {
511 field.ports.resize(std::max(0, node->dynamic_ports.num_in_ports(idx)));
515 if constexpr(avnd::dynamic_ports_output_introspection<T>::size > 0)
517 for(
auto state : node->impl.full_state())
519 avnd::dynamic_ports_output_introspection<T>::for_all_n2(
520 state.outputs, [&]<std::size_t Idx>(
521 auto& field,
auto, avnd::field_index<Idx> idx) {
522 field.ports.resize(std::max(0, node->dynamic_ports.num_out_ports(idx)));
528 if(
auto q = gcq.lock())
529 q->enqueue(Execution::gc(std::move(port_st)));
533 setup.proc_map[this->node.get()] = &element;
534 const std::size_t n_in = std::min(new_inlets.size(), inbuf.size());
535 for(std::size_t i = 0; i < n_in; i++)
536 setup.register_inlet(*new_inlets[i], inbuf[i], this->node, commands);
538 const std::size_t n_out = std::min(new_outlets.size(), outbuf.size());
539 for(std::size_t i = 0; i < n_out; i++)
540 setup.register_outlet(*new_outlets[i], outbuf[i], this->node, commands);
545 setup.connectCable(*c, commands);
546 this->portsReplaced(&commands);
550 this->m_oldInlets = new_inlets;
551 this->m_oldOutlets = new_outlets;
554 using dynamic_ports_port_type = avnd::dynamic_ports_input_introspection<Node>;
555 if constexpr(dynamic_ports_port_type::size > 0)
557 avnd::effect_container<Node>& eff = n->impl;
558 for(
auto state : eff.full_state())
560 dynamic_ports_port_type::for_all_n2(
568 void connect_controls(
570 std::shared_ptr<safe_node<Node>>& ptr)
572 using dynamic_ports_port_type = avnd::dynamic_ports_input_introspection<Node>;
573 using control_inputs_type = avnd::control_input_introspection<Node>;
574 using curve_inputs_type = avnd::curve_input_introspection<Node>;
575 using soundfile_inputs_type = avnd::soundfile_input_introspection<Node>;
576 using midifile_inputs_type = avnd::midifile_input_introspection<Node>;
577 using raw_file_inputs_type = avnd::raw_file_input_introspection<Node>;
578 using control_outputs_type = avnd::control_output_introspection<Node>;
581 safe_node<Node>& node = *ptr;
582 avnd::effect_container<Node>& eff = node.impl;
587 if constexpr(dynamic_ports_port_type::size > 0)
589 for(
auto state : eff.full_state())
591 dynamic_ports_port_type::for_all_n2(
595 if constexpr(control_inputs_type::size > 0)
597 for(
auto state : eff.full_state())
599 control_inputs_type::for_all_n2(
600 state.inputs, dispatch_control_setup<Node>{element, ctx, ptr, *this, this});
603 if constexpr(curve_inputs_type::size > 0)
605 for(
auto state : eff.full_state())
607 curve_inputs_type::for_all_n2(
608 state.inputs, dispatch_control_setup<Node>{element, ctx, ptr, *this, this});
611 if constexpr(soundfile_inputs_type::size > 0)
613 soundfile_inputs_type::for_all_n2(
614 avnd::get_inputs<Node>(eff),
615 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
617 setup_soundfile_task_pool(element, ctx, ptr);
619 if constexpr(midifile_inputs_type::size > 0)
621 midifile_inputs_type::for_all_n2(
622 avnd::get_inputs<Node>(eff),
623 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
625 if constexpr(raw_file_inputs_type::size > 0)
627 raw_file_inputs_type::for_all_n2(
628 avnd::get_inputs<Node>(eff),
629 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
633 if constexpr(control_inputs_type::size > 0 || control_outputs_type::size > 0)
636 if(settings.getExecutionUpdate())
639 std::weak_ptr<safe_node<Node>> weak_node = ptr;
640 update_control_value_in_ui<Node> timer_action{weak_node, &element};
643 con(ctx.doc.coarseUpdateTimer, &QTimer::timeout,
this,
644 [timer_action = std::move(timer_action)] { timer_action(); },
645 Qt::QueuedConnection);
650 void setup_soundfile_task_pool(
651 ProcessModel<Node>& element, const ::Execution::Context& ctx,
652 std::shared_ptr<safe_node<Node>>& ptr)
654 safe_node<Node>& node = *ptr;
656 using soundfile_inputs_type = avnd::soundfile_input_introspection<Node>;
658 auto& tq = score::TaskPool::instance();
659 node.soundfiles.load_request
660 = [&tq, p = std::weak_ptr{ptr}, &ctx](std::string& str,
int idx) {
661 auto eff_ptr = p.lock();
664 tq.post([eff_ptr = std::move(eff_ptr), filename = str, &ctx, idx]()
mutable {
665 if(
auto file = loadSoundfile(filename, ctx.doc, ctx.execState))
668 QMetaObject::invokeMethod(
669 QCoreApplication::instance(),
670 [&ctx, file = std::move(file), eff_ptr = std::move(eff_ptr), idx = idx] {
671 ctx.executionQueue.enqueue(
672 [sf = std::move(file), p = std::weak_ptr{eff_ptr}, idx]()
mutable {
673 auto eff_ptr = p.lock();
677 avnd::effect_container<Node>& eff = eff_ptr->impl;
678 soundfile_inputs_type::for_nth_mapped_n2(
679 avnd::get_inputs<Node>(eff), idx,
680 [&]<std::size_t NField, std::size_t N>(
681 auto& field, avnd::predicate_index<N> p,
682 avnd::field_index<NField> f) {
683 sf = eff_ptr->soundfile_loaded(sf, p, f);
692 void connect_dynamic_items(ProcessModel<Node>& element, Node& eff)
696 avnd::inputs_is_type<Node>
697 || avnd::control_input_introspection<Node>::size == 0)
700 avnd::control_input_introspection<Node>::for_all_n2(
701 avnd::get_inputs<Node>(eff),
702 [&element]<std::size_t Idx,
typename F>(
703 F& field,
auto pred_index, avnd::field_index<Idx>) {
704 if constexpr(avnd::dynamic_items_parameter<F>)
706 auto ports = element.avnd_input_idx_to_model_ports(Idx);
707 if(ports.size() != 1)
709 if(
auto combo = qobject_cast<Process::ComboBox*>(ports[0]))
711 field.update_items = [p = QPointer<Process::ComboBox>{combo}](
712 std::vector<std::string> items) {
713 std::vector<std::pair<QString, ossia::value>> alts;
714 alts.reserve(items.size());
715 for(std::size_t i = 0; i < items.size(); i++)
716 alts.emplace_back(QString::fromStdString(items[i]), (int)i);
717 QMetaObject::invokeMethod(
719 [p, alts = std::move(alts)]()
mutable {
721 p->setAlternatives(std::move(alts));
723 Qt::QueuedConnection);
730 void connect_message_bus(
731 ProcessModel<Node>& element, const ::Execution::Context& ctx, Node& eff)
734 if constexpr(avnd::has_gui_to_processor_bus<Node>)
736 element.from_ui = [qex_ptr = weak_exec, &eff](QByteArray b) {
737 auto qex = qex_ptr.lock();
741 qex->enqueue([mess = std::move(b), &eff]()
mutable {
742 using refl = avnd::function_reflection<&Node::process_message>;
743 static_assert(refl::count <= 1);
745 if constexpr(refl::count == 0)
748 eff.process_message();
750 else if constexpr(refl::count == 1)
752 using arg_type = avnd::first_argument<&Node::process_message>;
753 std::decay_t<arg_type> arg;
754 MessageBusReader reader{mess};
756 eff.process_message(std::move(arg));
762 if constexpr(avnd::has_processor_to_gui_bus<Node>)
764 if constexpr(
requires { eff.send_message = [](
auto&&) { }; })
766 eff.send_message = [proc = QPointer{&this->process()},
767 qed_ptr = weak_edit]<
typename T>(T&& b)
mutable {
768 auto qed = qed_ptr.lock();
772 sizeof(QPointer<QObject>) +
sizeof(b)
773 < Execution::ExecutionCommand::max_storage)
775 qed->enqueue([proc, bb = std::move(b)]()
mutable {
776 if(proc && proc->to_ui)
777 MessageBusSender{proc->to_ui}(std::move(bb));
783 [proc, bb = std::make_unique<std::decay_t<T>>(std::move(b))]()
mutable {
784 if(proc && proc->to_ui)
785 MessageBusSender{proc->to_ui}(*std::move(bb));
790 else if constexpr(
requires { eff.send_message = []() { }; })
793 = [proc = QPointer{&this->process()}, qed_ptr = weak_edit]()
mutable {
796 auto qed = qed_ptr.lock();
800 qed->enqueue([proc]()
mutable {
801 if(proc && proc->to_ui)
802 MessageBusSender{proc->to_ui}();
809 void connect_worker(const ::Execution::Context& ctx, avnd::effect_container<Node>& eff)
811 if constexpr(avnd::has_worker<Node>)
814 auto& tq = score::TaskPool::instance();
815 using worker_type =
decltype(eff.effect.worker);
816 for(
auto& eff : eff.effects())
818 std::weak_ptr eff_ptr = std::shared_ptr<Node>(this->node, &eff);
819 std::weak_ptr qex_ptr = std::shared_ptr<Execution::ExecutionCommandQueue>(
820 ctx.alias.lock(), &ctx.executionQueue);
823 = [&tq, qex_ptr = std::move(qex_ptr),
824 eff_ptr = std::move(eff_ptr)]<
typename... Args>(Args&&... f)
mutable {
827 tq.post([eff_ptr, qex_ptr, ... ff = std::forward<Args>(f)]()
mutable {
835 =
decltype(worker_type::work(std::forward<
decltype(ff)>(ff)...));
836 if constexpr(std::is_void_v<type_of_result>)
838 worker_type::work(std::forward<
decltype(ff)>(ff)...);
844 auto res = worker_type::work(std::forward<
decltype(ff)>(ff)...);
850 ossia::qt::run_async(
851 qApp, [eff_ptr = std::move(eff_ptr), qex_ptr = std::move(qex_ptr),
852 res = std::move(res)]()
mutable {
854 std::shared_ptr qex = qex_ptr.lock();
859 [eff_ptr = std::move(eff_ptr), res = std::move(res)]()
mutable {
863 if(
auto p = eff_ptr.lock())
875 void update_controls(std::shared_ptr<safe_node<Node>>& ptr)
877 avnd::effect_container<Node>& eff = ptr->impl;
879 for(
auto state : eff.full_state())
881 avnd::input_introspection<Node>::for_all(
882 state.inputs, [&](
auto& field) { if_possible(field.update(state.effect)); });
887 void cleanup()
override
889 if constexpr(
requires { this->process().from_ui; })
891 this->process().from_ui = [](QByteArray arr) {};
896 if constexpr(is_gpu<Node>)
899 auto& gfx_exec = this->system().doc.template plugin<Gfx::DocumentPlugin>().exec;
902 gfx_exec.ui->unregister_node(node_id);
903 node_id = score::gfx::invalid_node_index;
908 for(
auto* outlet : this->process().outlets())
910 if(
auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
916 ::Execution::ProcessComponent::cleanup();