3 #include <Process/Execution/ProcessComponent.hpp>
4 #include <Process/ExecutionContext.hpp>
6 #include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
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>
18 #include <score/tools/Bind.hpp>
20 #include <ossia/dataflow/exec_state_facade.hpp>
21 #include <ossia/dataflow/node_process.hpp>
23 #include <ossia-qt/invoke.hpp>
25 #include <QGuiApplication>
28 #include <Crousti/GpuNode.hpp>
29 #include <Gfx/GfxApplicationPlugin.hpp>
32 #include <Media/AudioDecoder.hpp>
34 #include <score/tools/ThreadPool.hpp>
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>
52 [[nodiscard]]
static QString
55 if(
auto str = value.target<std::string>())
61 [[nodiscard]]
static auto
65 if(
auto str = filenameFromPort(value, ctx); !str.isEmpty())
67 auto dec = Media::AudioDecoder::decode_synchronous(str, rate);
71 auto hdl = std::make_shared<ossia::audio_data>();
72 hdl->data = std::move(dec->second);
73 hdl->path = str.toStdString();
78 return ossia::audio_handle{};
81 using midifile_handle = std::shared_ptr<oscr::midifile_data>;
82 [[nodiscard]]
inline midifile_handle
86 if(
auto str = filenameFromPort(value, ctx); !str.isEmpty())
89 if(!f.open(QIODevice::ReadOnly))
91 auto ptr = f.map(0, f.size());
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)
98 hdl->filename = str.toStdString();
104 using raw_file_handle = std::shared_ptr<raw_file_data>;
105 [[nodiscard]]
inline raw_file_handle loadRawfile(
109 if(
auto filename = filenameFromPort(value, ctx); !filename.isEmpty())
111 if(!QFile::exists(filename))
114 auto hdl = std::make_shared<oscr::raw_file_data>();
115 hdl->file.setFileName(filename);
116 if(!hdl->file.open(QIODevice::ReadOnly))
121 auto map = (
char*)hdl->file.map(0, hdl->file.size());
122 hdl->data = QByteArray::fromRawData(map, hdl->file.size());
127 hdl->file.setTextModeEnabled(
true);
129 hdl->data = hdl->file.readAll();
131 hdl->filename = filename.toStdString();
136 [[nodiscard]]
inline auto loadSoundfile(
138 const std::shared_ptr<ossia::execution_state>& st)
140 const double rate = ossia::exec_state_facade{st.get()}.sampleRate();
141 return loadSoundfile(value, ctx, rate);
145 template <
typename ExecNode_T,
typename T, std::
size_t ControlN>
148 std::weak_ptr<ExecNode_T> weak_node;
152 if(
auto n = weak_node.lock())
154 n->template control_updated_from_ui<T, ControlN>(std::move(v));
159 template <
typename Node>
162 using ExecNode = safe_node<Node>;
167 const std::shared_ptr<ExecNode>& node_ptr;
170 template <
typename Field, std::
size_t NPred, std::
size_t NField>
174 std::weak_ptr<ExecNode> weak_node;
176 void operator()(
const ossia::value& val)
178 constexpr
auto control_index = NPred;
180 using control_value_type = std::decay_t<decltype(Field::value)>;
182 if(
auto node = weak_node.lock())
184 control_value_type v;
185 node->from_ossia_value(field, val, v, avnd::field_index<NField>{});
188 weak_node, std::move(v)});
193 template <
typename Field, std::
size_t N, std::
size_t NField>
195 operator()(Field& param, avnd::predicate_index<N> np, avnd::field_index<NField> nf)
201 if constexpr(!requires { param.value.reset(); })
202 node_ptr->from_ossia_value(param, inlet->value(), param.value, nf);
205 avnd::effect_container<Node>& eff = node_ptr->impl;
207 for(
auto& state : eff.full_state())
209 if_possible(param.update(state.effect));
215 std::weak_ptr<ExecNode> weak_node = node_ptr;
217 inlet, &Process::ControlInlet::valueChanged, parent,
218 con_unvalidated<Field, N, NField>{ctx, weak_node, param});
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>)
227 = safe_cast<Process::ControlInlet*>(modelPort<Node>(element.inlets(), NField));
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>{});
235 std::weak_ptr<ExecNode> weak_node = node_ptr;
236 std::weak_ptr<ossia::execution_state> weak_st = ctx.execState;
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))
245 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
246 auto n = weak_node.lock();
253 f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
259 template <avnd::m
idifile_port Field, std::
size_t N, std::
size_t NField>
260 void operator()(Field& param, avnd::predicate_index<N>, avnd::field_index<NField>)
263 = safe_cast<Process::ControlInlet*>(modelPort<Node>(element.inlets(), NField));
266 if(
auto hdl = loadMidifile(inlet->value(), ctx.doc))
267 node_ptr->midifile_loaded(
268 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
271 std::weak_ptr<ExecNode> weak_node = node_ptr;
272 std::weak_ptr<ossia::execution_state> weak_st = ctx.execState;
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))
280 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
281 auto n = weak_node.lock();
288 f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
294 template <
typename Field>
295 static auto executePortPreprocess(
auto& file)
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);
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>)
307 = safe_cast<Process::ControlInlet*>(modelPort<Node>(element.inlets(), NField));
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; };
315 if(
auto hdl = loadRawfile(inlet->value(), ctx.doc, has_text, has_mmap))
317 if constexpr(avnd::port_can_process<Field>)
321 auto func = executePortPreprocess<Field>(*hdl);
322 node_ptr->file_loaded(
323 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
325 func(node_ptr->impl.effect);
329 node_ptr->file_loaded(
330 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
335 std::weak_ptr<ExecNode> weak_node = node_ptr;
336 std::weak_ptr<ossia::execution_state> weak_st = ctx.execState;
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))
343 if constexpr(avnd::port_can_process<Field>)
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();
354 n->file_loaded(f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
361 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
362 auto n = weak_node.lock();
368 n->file_loaded(f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
376 template <
typename Node>
379 using ExecNode = safe_node<Node>;
382 typename ExecNode::control_input_values_type& arr;
385 template <std::
size_t N, std::
size_t NField>
386 void operator()(
auto& field, avnd::predicate_index<N>, avnd::field_index<NField>)
388 if(
auto p = modelPort<Node>(element.inlets(), NField))
390 inlet->setExecutionValue(oscr::to_ossia_value(field, field.value));
394 template <
typename Node>
397 using ExecNode = safe_node<Node>;
399 typename ExecNode::control_output_values_type& arr;
402 template <std::
size_t N, std::
size_t NField>
403 void operator()(
auto& field, avnd::predicate_index<N>, avnd::field_index<NField>)
406 = safe_cast<Process::ControlOutlet*>(modelPort<Node>(element.outlets(), NField));
407 outlet->setValue(oscr::to_ossia_value(field, field.value));
411 template <
typename Node>
414 using ExecNode = safe_node<Node>;
416 std::weak_ptr<ExecNode> weak_node;
419 void handle_controls(ExecNode& node)
const noexcept
421 using namespace ossia::safe_nodes;
425 typename ExecNode::control_input_values_type arr;
427 while(node.control.ins_queue.try_dequeue(arr))
433 for(
auto& state : node.impl.full_state())
435 avnd::control_input_introspection<Node>::for_all_n2(
441 void handle_control_outs(ExecNode& node)
const noexcept
443 using namespace ossia::safe_nodes;
446 typename ExecNode::control_output_values_type arr;
448 while(node.control.outs_queue.try_dequeue(arr))
454 avnd::control_output_introspection<Node>::for_all_n2(
459 void operator()()
const noexcept
461 if(
auto node = weak_node.lock())
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);
469 if constexpr(control_out_count > 0)
470 handle_control_outs(*node);
475 template <
typename T,
bool Predicate>
477 template <
typename T>
486 template <
typename U>
490 template <
typename U>
491 T& operator=(U&& u) noexcept
497 template <
typename T>
500 [[no_unique_address]] T value;
508 template <
typename U>
510 : value{std::forward<U>(other)}
514 operator const T&()
const noexcept {
return value; }
515 operator T&() noexcept {
return value; }
516 operator T&&() && noexcept {
return std::move(value); }
518 template <
typename U>
519 T& operator=(U&& u) noexcept
521 return value = std::forward<U>(u);
525 template <
typename Node>
528 using node_process::node_process;
529 void start()
override
531 node_process::start();
532 auto& n =
static_cast<safe_node<Node>&
>(*node);
533 n.impl.effect.start();
537 auto& n =
static_cast<safe_node<Node>&
>(*node);
538 n.impl.effect.stop();
539 node_process::stop();
543 template <
typename Node>
550 return uuid_from_string<Node>();
557 return static_key() == other || Execution::ProcessComponent::base_key_match(other);
564 element, ctx,
"Executor::ProcessModel<Info>", p}
569 if constexpr(is_gpu<Node>)
574 auto node = std::make_shared<Gfx::gfx_exec_node>(gfx_exec);
575 node->prepare(*ctx.execState);
581 for(
auto& ctl : element.inlets())
583 if(
auto ctrl = qobject_cast<Process::ControlInlet*>(ctl))
585 auto& p = node->add_control();
586 p->value = ctrl->value();
590 ctrl, &Process::ControlInlet::valueChanged,
this,
594 else if(
auto ctrl = qobject_cast<Process::ValueInlet*>(ctl))
596 auto& p = node->add_control();
600 else if(
auto ctrl = qobject_cast<Process::AudioInlet*>(ctl))
604 else if(
auto ctrl = qobject_cast<Gfx::TextureInlet*>(ctl))
611 for(
auto* outlet : element.outlets())
613 if(
auto ctrl = qobject_cast<Process::ControlOutlet*>(outlet))
615 node->add_control_out();
617 else if(
auto ctrl = qobject_cast<Process::ValueOutlet*>(outlet))
619 node->add_control_out();
621 else if(
auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
623 node->add_texture_out();
624 out->nodeId = node_id;
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>)
635 auto gpu_node =
new CustomGpuNode<Node>(qex_ptr, node->control_outs,
id);
638 else if constexpr(GpuComputeNode2<Node>)
640 auto gpu_node =
new GpuComputeNode<Node>(qex_ptr, node->control_outs,
id);
643 else if constexpr(GpuNode<Node>)
645 auto gpu_node =
new GfxNode<Node>(element, qex_ptr, node->control_outs,
id);
648 node->id = gfx_exec.ui->register_node(std::move(ptr));
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());
659 if_possible(node->impl.effect.ossia_state = st);
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);
670 connect_controls(element, ctx, ptr);
671 update_controls(ptr);
674 node->audio_configuration_changed();
677 if constexpr(avnd::tag_process_exec<Node>)
679 this->m_ossia_process = std::make_shared<CustomNodeProcess<Node>>(this->node);
683 this->m_ossia_process = std::make_shared<ossia::node_process>(this->node);
687 void connect_controls(
689 std::shared_ptr<safe_node<Node>>& ptr)
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>;
699 safe_node<Node>& node = *ptr;
700 avnd::effect_container<Node>& eff = node.impl;
702 if constexpr(control_inputs_type::size > 0)
707 for(
auto& state : eff.full_state())
709 control_inputs_type::for_all_n2(
713 if constexpr(curve_inputs_type::size > 0)
718 for(
auto& state : eff.full_state())
720 curve_inputs_type::for_all_n2(
724 if constexpr(soundfile_inputs_type::size > 0)
726 soundfile_inputs_type::for_all_n2(
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();
735 tq.post([eff_ptr = std::move(eff_ptr), filename = str, &ctx, idx]()
mutable {
736 if(
auto file = loadSoundfile(filename, ctx.doc, ctx.execState))
738 ctx.executionQueue.enqueue(
739 [sf = std::move(file), p = std::weak_ptr{eff_ptr}, idx]()
mutable {
740 auto eff_ptr = p.lock();
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);
757 if constexpr(midifile_inputs_type::size > 0)
759 midifile_inputs_type::for_all_n2(
762 if constexpr(raw_file_inputs_type::size > 0)
764 raw_file_inputs_type::for_all_n2(
769 if constexpr(control_inputs_type::size > 0 || control_outputs_type::size > 0)
772 std::weak_ptr<safe_node<Node>> weak_node = ptr;
777 ctx.doc.coarseUpdateTimer, &QTimer::timeout,
this, [=] { timer_action(); },
778 Qt::QueuedConnection);
782 void connect_message_bus(
786 if constexpr(avnd::has_gui_to_processor_bus<Node>)
788 element.from_ui = [p = QPointer{
this}, &eff](QByteArray b) {
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);
796 if constexpr(refl::count == 0)
799 eff.process_message();
801 else if constexpr(refl::count == 1)
803 using arg_type = avnd::first_argument<&Node::process_message>;
804 std::decay_t<arg_type> arg;
807 eff.process_message(std::move(arg));
813 if constexpr(avnd::has_processor_to_gui_bus<Node>)
815 eff.send_message = [
this](
auto b)
mutable {
816 this->in_edit([
this, bb = std::move(b)]()
mutable {
817 if(this->process().to_ui)
824 void connect_worker(const ::Execution::Context& ctx, avnd::effect_container<Node>& eff)
826 if constexpr(avnd::has_worker<Node>)
829 auto& tq = score::TaskPool::instance();
830 using worker_type = decltype(eff.effect.worker);
831 for(
auto& eff : eff.effects())
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);
838 = [&tq, qex_ptr = std::move(qex_ptr),
839 eff_ptr = std::move(eff_ptr)]<
typename... Args>(Args&&... f) {
842 tq.post([eff_ptr = std::move(eff_ptr), qex_ptr = std::move(qex_ptr),
843 ... ff = std::forward<Args>(f)]()
mutable {
851 = decltype(worker_type::work(std::forward<decltype(ff)>(ff)...));
852 if constexpr(std::is_void_v<type_of_result>)
854 worker_type::work(std::forward<decltype(ff)>(ff)...);
860 auto res = worker_type::work(std::forward<decltype(ff)>(ff)...);
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 {
870 std::shared_ptr qex = qex_ptr.lock();
875 [eff_ptr = std::move(eff_ptr), res = std::move(res)]()
mutable {
879 if(
auto p = eff_ptr.lock())
891 void update_controls(std::shared_ptr<safe_node<Node>>& ptr)
893 avnd::effect_container<Node>& eff = ptr->impl;
895 for(
auto& state : eff.full_state())
897 avnd::input_introspection<Node>::for_all(
898 state.inputs, [&](
auto& field) { if_possible(field.update(state.effect)); });
903 void cleanup()
override
905 if constexpr(requires { this->process().from_ui; })
907 this->process().from_ui = [](QByteArray arr) {};
912 if constexpr(is_gpu<Node>)
915 auto& gfx_exec = this->system().doc.template plugin<Gfx::DocumentPlugin>().exec;
917 gfx_exec.ui->unregister_node(node_id);
921 for(
auto* outlet : this->process().outlets())
923 if(
auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
929 ::Execution::ProcessComponent::cleanup();
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