Loading...
Searching...
No Matches
score-plugin-avnd/Crousti/ProcessModel.hpp
1#pragma once
2#include <Process/GenericProcessFactory.hpp>
3#include <Process/Process.hpp>
4#include <Process/ProcessFactory.hpp>
5
6#include <Crousti/Concepts.hpp>
7#include <Crousti/MessageBus.hpp>
8#include <Crousti/Metadata.hpp>
9#include <Crousti/Metadatas.hpp>
10#include <Crousti/ProcessModelPortInit.hpp>
11#include <Dataflow/Commands/CableHelpers.hpp>
12
13#include <score/serialization/MapSerialization.hpp>
14#include <score/tools/std/HashMap.hpp>
15
16#include <core/document/Document.hpp>
17
18#include <ossia/detail/algorithms.hpp>
19#include <ossia/detail/string_map.hpp>
20#include <ossia/detail/type_if.hpp>
21#include <ossia/detail/typelist.hpp>
22
23#include <score/tools/File.hpp>
24
25#include <QFileInfo>
26#include <QTimer>
27
28#include <avnd/binding/ossia/data_node.hpp>
29#include <avnd/binding/ossia/dynamic_ports.hpp>
30#include <avnd/common/for_nth.hpp>
31#include <avnd/concepts/gfx.hpp>
32#include <avnd/concepts/ui.hpp>
33#include <avnd/introspection/messages.hpp>
34#include <avnd/wrappers/bus_host_process_adapter.hpp>
35
36#include <score_plugin_engine.hpp>
37
42namespace oscr
43{
44
45template <typename Info>
47{
48};
49
50template <typename Info>
54
55namespace
56{
57struct dummy_ui_callback
58{
59 void operator()(const QByteArray& arr) noexcept { }
60};
61}
62
63template <avnd::has_processor_to_gui_bus Info>
65{
66 std::function<void(QByteArray)> to_ui = dummy_ui_callback{};
67};
68
69template <avnd::has_gui_to_processor_bus Info>
71{
72 std::function<void(QByteArray)> from_ui = dummy_ui_callback{};
73};
74
75inline void hideAllInlets(Process::ProcessModel& proc)
76{
77 for(auto& p : proc.inlets())
78 p->displayHandledExplicitly = true;
79 for(auto& p : proc.outlets())
80 p->displayHandledExplicitly = true;
81}
82
83template <typename Info>
84class ProcessModel final
86 , public MessageBusWrapperFromUi<Info>
87 , public MessageBusWrapperToUi<Info>
88{
89 SCORE_SERIALIZE_FRIENDS
90 PROCESS_METADATA_IMPL(ProcessModel<Info>)
91 friend struct TSerializer<DataStream, oscr::ProcessModel<Info>>;
92 friend struct TSerializer<JSONObject, oscr::ProcessModel<Info>>;
93
94public:
95 [[no_unique_address]]
96 oscr::dynamic_ports_storage<Info> dynamic_ports;
97
98 [[no_unique_address]]
99 ossia::type_if<Info, oscr::has_ports_callbacks<Info>>
100 object_storage_for_ports_callbacks;
101
103 const TimeVal& duration, const Id<Process::ProcessModel>& id,
104 const score::DocumentContext& ctx, QObject* parent)
106 duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
107 {
108 metadata().setInstanceName(*this);
109
110 init_common();
111 init_before_port_creation();
112 init_all_ports();
113 init_after_port_creation();
114 }
115
117 const TimeVal& duration, const QString& custom,
118 const Id<Process::ProcessModel>& id, QObject* parent)
120 duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
121 {
122 metadata().setInstanceName(*this);
123
124 init_common();
125 init_before_port_creation();
126 init_all_ports();
127
128 if constexpr(avnd::file_input_introspection<Info>::size > 0)
129 {
130 static constexpr auto idx
131 = avnd::file_input_introspection<Info>::index_to_field_index(0);
132 setupInitialStringPort(idx, custom);
133 }
134 else if constexpr(avnd::control_input_introspection<Info>::size > 0)
135 {
136 static constexpr auto idx
137 = avnd::control_input_introspection<Info>::index_to_field_index(0);
138 using type =
139 typename avnd::control_input_introspection<Info>::template nth_element<0>;
140 if constexpr(avnd::string_ish<decltype(type::value)>)
141 setupInitialStringPort(idx, custom);
142 }
143 init_after_port_creation();
144 }
145
146 void setupInitialStringPort(int idx, const QString& custom) noexcept
147 {
148 Process::Inlet* port = avnd_input_idx_to_model_ports(idx)[0];
149 auto pp = safe_cast<Process::ControlInlet*>(port);
150
151 if(auto val = pp->value(); bool(val.target<std::string>()))
152 {
153 pp->setValue(custom.toStdString());
154 }
155 }
156
157 template <typename Impl>
158 explicit ProcessModel(Impl& vis, QObject* parent)
159 : Process::ProcessModel{vis, parent}
160 {
161 init_common();
162 init_before_port_creation();
163 vis.writeTo(*this);
164 check_all_ports();
165 init_after_port_creation();
166 }
167
168 ~ProcessModel() override { }
169
170private:
171 void init_common()
172 {
173 if constexpr(avnd::tag_loops_by_default<Info>)
174 setLoops(true);
175 }
176
177 void init_before_port_creation() { init_dynamic_ports(); }
178 void init_after_port_creation()
179 {
180 init_controller_ports();
181 init_folder_comboboxes();
182 }
183
184 // Folder-backed comboboxes (halp::folder_combobox): each carries the name of
185 // a sibling folder port; list that folder's files as the combobox items, at
186 // edit/load time, and refresh whenever the folder value changes. Runs after
187 // deserialization, so a loaded document's folder value is already in place.
188 void init_folder_comboboxes()
189 {
190 for(auto* inl : m_inlets)
191 {
192 auto combo = qobject_cast<Process::ComboBox*>(inl);
193 if(!combo || combo->folderPortName.isEmpty())
194 continue;
195
196 Process::ControlInlet* folderPort = nullptr;
197 for(auto* other : m_inlets)
198 {
199 auto ci = qobject_cast<Process::ControlInlet*>(other);
200 if(ci && ci != combo && ci->name() == combo->folderPortName)
201 {
202 folderPort = ci;
203 break;
204 }
205 }
206
207 auto pathOf = [this](Process::ControlInlet* p) -> QString {
208 if(!p)
209 return {};
210 if(auto s = p->value().target<std::string>())
211 {
212 // Stored paths are templates (<PROJECT>:..., <LIBRARY>:..., or
213 // document-relative); resolve before touching the filesystem, or a
214 // project-relative folder lists nothing.
215 auto path = QString::fromStdString(*s);
216 if(path.isEmpty())
217 return {};
218 // Walk up rather than score::IDocument::documentFromObject, which
219 // throws when there is no document -- ports are built before the
220 // process is attached to one.
221 score::Document* doc{};
222 for(QObject* o = this->parent(); o && !doc; o = o->parent())
223 doc = qobject_cast<score::Document*>(o);
224 if(doc)
225 path = score::locateFilePath(path, doc->context());
226 // The sibling port may name a folder, or a file (e.g. a sound-file
227 // port): in the latter case list the file's containing folder.
228 if(QFileInfo fi{path}; fi.isFile())
229 return fi.absolutePath();
230 return path;
231 }
232 return {};
233 };
234
235 combo->repopulateFromFolder(pathOf(folderPort));
236 if(folderPort)
237 QObject::connect(
238 folderPort, &Process::ControlInlet::valueChanged, combo,
239 [combo, folderPort, pathOf](const ossia::value&) {
240 combo->repopulateFromFolder(pathOf(folderPort));
241 });
242 }
243 }
244 void check_all_ports()
245 {
246 if(std::ssize(m_inlets) != expected_input_ports()
247 || std::ssize(m_outlets) != expected_output_ports())
248 {
249 qDebug() << typeid(Info).name() << this->metadata().getName()
250 << ": WARNING : process does not match spec: I " << m_inlets.size()
251 << "but expected: " << expected_input_ports() << " ; O "
252 << m_outlets.size() << "but expected: " << expected_output_ports();
253
254 std::vector<Dataflow::SavedPort> m_oldInlets, m_oldOutlets;
255 for(auto& port : m_inlets)
256 m_oldInlets.emplace_back(
257 Dataflow::SavedPort{port->name(), port->type(), port->saveData()});
258 for(auto& port : m_outlets)
259 m_oldOutlets.emplace_back(
260 Dataflow::SavedPort{port->name(), port->type(), port->saveData()});
261
262 qDeleteAll(m_inlets);
263 m_inlets.clear();
264 qDeleteAll(m_outlets);
265 m_outlets.clear();
266
267 init_all_ports();
268
269 Dataflow::reloadPortsInNewProcess(m_oldInlets, m_oldOutlets, *this);
270 }
271 }
272
280 template <typename F, std::size_t Idx>
281 Process::ControlInlet* upgradeControllerPort(F& field, avnd::field_index<Idx> idx)
282 {
283 auto ports = avnd_input_idx_to_model_ports(Idx);
284 SCORE_ASSERT(ports.size() == 1);
285 auto old_inlet = qobject_cast<Process::ControlInlet*>(ports[0]);
286 SCORE_ASSERT(old_inlet);
287
288 Process::Inlets fresh;
289 InletInitFunc<Info> make{*this, fresh};
290 make.inlet = old_inlet->id().val();
291 make(field, idx);
292 if(fresh.size() != 1)
293 {
294 qDeleteAll(fresh);
295 return old_inlet;
296 }
297
298 auto new_inlet = qobject_cast<Process::ControlInlet*>(fresh[0]);
299 if(!new_inlet || new_inlet->concreteKey() == old_inlet->concreteKey())
300 {
301 qDeleteAll(fresh);
302 return old_inlet;
303 }
304
305 new_inlet->loadData(old_inlet->saveData(), Process::PortLoadDataFlags::ReloadValue);
306 new_inlet->setExposed(old_inlet->exposed());
307 new_inlet->setDescription(old_inlet->description());
308 new_inlet->displayHandledExplicitly = old_inlet->displayHandledExplicitly;
309
310 auto it = ossia::find(m_inlets, old_inlet);
311 SCORE_ASSERT(it != m_inlets.end());
312 *it = new_inlet;
313 delete old_inlet;
314 return new_inlet;
315 }
316
317 void init_controller_ports()
318 {
320 {
321 avnd::control_input_introspection<Info>::for_all_n2(
322 avnd::get_inputs<Info>((Info&)this->object_storage_for_ports_callbacks),
323 [this]<std::size_t Idx, typename F>(
324 F& field, auto pred_index, avnd::field_index<Idx> idx) {
325 Info& obj = this->object_storage_for_ports_callbacks;
326 if constexpr(requires { F::on_controller_interaction(); })
327 {
328 upgradeControllerPort(field, idx);
329 // Version migration for controls whose old document representation
330 // differs from their current value type (e.g. newline keywords).
331 if constexpr(requires(ossia::value v) { F::migrate_value(v); })
332 {
333 auto inlet = qobject_cast<Process::ControlInlet*>(
334 avnd_input_idx_to_model_ports(Idx)[0]);
335 inlet->setValue(F::migrate_value(inlet->value()));
336 }
337 }
338 if constexpr(requires { F::on_controller_setup(); })
339 {
340 auto controller_inlets = avnd_input_idx_to_model_ports(Idx);
341 SCORE_ASSERT(controller_inlets.size() == 1);
342 auto inlet = qobject_cast<Process::ControlInlet*>(controller_inlets[0]);
343
344 oscr::from_ossia_value(inlet->value(), field.value);
345
346 if_possible(field.update(obj));
347
348 F::on_controller_setup()(obj, field.value);
349 }
350 if constexpr(requires { F::on_controller_interaction(); })
351 {
352 auto controller_inlets = avnd_input_idx_to_model_ports(Idx);
353 SCORE_ASSERT(controller_inlets.size() == 1);
354 auto inlet = qobject_cast<Process::ControlInlet*>(controller_inlets[0]);
355 inlet->noValueChangeOnMove = true;
356
357 if constexpr(!requires { F::on_controller_setup(); })
358 {
359 oscr::from_ossia_value(inlet->value(), field.value);
360 if_possible(field.update(obj));
361 F::on_controller_interaction()(obj, field.value);
362 }
363
364 connect(
365 inlet, &Process::ControlInlet::valueChanged,
366 [this, &field](const ossia::value& val) {
367 Info& obj = this->object_storage_for_ports_callbacks;
368 oscr::from_ossia_value(val, field.value);
369
370 if_possible(field.update(obj));
371
372 F::on_controller_interaction()(obj, field.value);
373 });
374 }
375 });
376
377 if constexpr(avnd::has_gui_to_processor_bus<Info>)
378 {
379 // FIXME needs to be a list of callbacks?
380 }
381
382 if constexpr(avnd::has_processor_to_gui_bus<Info>)
383 {
384 Info& obj = this->object_storage_for_ports_callbacks;
385
386 obj.send_message = [this]<typename T>(T&& b) mutable {
387 if(this->to_ui)
388 MessageBusSender{this->to_ui}(std::move(b));
389 };
390 }
391 }
392 }
393
394 void init_dynamic_ports()
395 {
396 // To check:
397
398 // - serialization
399 // - OK: uses of total_input_count / total_output_count
400
401 // - OK: uses of oscr::modelPort (btw this does not seem to match total_input_count)
402
403 // - execution, resize the avnd object with the number of ports
404 // -> connect controls so that they change in the ui
405
406 // - OK: Layer::createControl: uses index_in_struct and assumes it's a ProcessModel::inlet index
407
408 // How are things going to happen UI-wise: all controls created one after each other ?
409 // what if we want to create separate tabs with e.g.
410 // control A 1, control B 1
411 // control A 2, control B 2
412 // we need to know the current state of dynamic ports
413
414 // UI: recreating UI causes crash when editing e.g. context menu of spinbox
415 // UI: recreating UI causes bug when editing e.g. spinbox
416
417 // -> we have to find a way to detect that the currently edited object is being deleted
418
419 // other option: mark widget as "unsafe", do not have it send value changes until mouse is released
420
421 // REloading on crash: does crash, because the control change does not have
422 // the time to be applied. In DocumentBuilder::loadCommandStack we have
423 // to process the event loop in-between events.
424 // FIxed if we put zero but then changing the widget with the mouses crashes because of the comment
425 // below...
426 // ->
427 // terminate called after throwing an instance of 'std::runtime_error'
428 // what(): Ongoing command mismatch: current command SetControlValue does not match new command MoveNodes
429
430 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
431 {
432 Info& obj = object_storage_for_ports_callbacks;
433 avnd::dynamic_ports_input_introspection<Info>::for_all_n2(
434 avnd::get_inputs(obj),
435 [&]<std::size_t N>(auto& port, auto pred_idx, avnd::field_index<N> field_idx) {
436 port.request_port_resize = [this, &port](int new_count) {
437 this->request_new_dynamic_input_count(port, avnd::field_index<N>{}, new_count);
438 };
439 if constexpr(requires { port.request_port_rows; })
440 port.request_port_rows = [this, &port](const auto& rows) {
441 this->template request_dynamic_rows<true>(
442 port, avnd::field_index<N>{}, rows);
443 };
444 });
445 }
446
447 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
448 {
449 Info& obj = object_storage_for_ports_callbacks;
450 avnd::dynamic_ports_output_introspection<Info>::for_all_n2(
451 avnd::get_outputs(obj),
452 [&]<std::size_t N>(auto& port, auto pred_idx, avnd::field_index<N> field_idx) {
453 port.request_port_resize = [this, &port](int new_count) {
454 this->request_new_dynamic_output_count(
455 port, avnd::field_index<N>{}, new_count);
456 };
457 if constexpr(requires { port.request_port_rows; })
458 port.request_port_rows = [this, &port](const auto& rows) {
459 this->template request_dynamic_rows<false>(
460 port, avnd::field_index<N>{}, rows);
461 };
462 });
463 }
464 }
465
466 template <bool Input, typename P, std::size_t N, typename Rows>
467 void request_dynamic_rows(P& port, avnd::field_index<N> idx, const Rows& rows)
468 {
469 if(rows.size() > 512)
470 return;
471 std::vector<int> keys;
472 keys.reserve(rows.size());
473 for(const auto& [key, text] : rows)
474 {
475 if(key < 10000 || ossia::contains(keys, key))
476 return;
477 keys.push_back(key);
478 }
479 auto& all = [&]() -> auto& {
480 if constexpr(Input)
481 return m_inlets;
482 else
483 return m_outlets;
484 }();
485 auto first = [&] {
486 if constexpr(Input)
487 return avnd_input_idx_to_iterator(idx);
488 else
489 return avnd_output_idx_to_iterator(idx);
490 }();
491 auto& count = [&]() -> auto& {
492 if constexpr(Input)
493 return dynamic_ports.num_in_ports(idx);
494 else
495 return dynamic_ports.num_out_ports(idx);
496 }();
497 using Ports = std::decay_t<decltype(all)>;
498 using Port = std::remove_pointer_t<typename Ports::value_type>;
499 Ports old{first, first + count}, next;
500 next.reserve(rows.size());
501 for(const auto& [key, text] : rows)
502 {
503 auto found
504 = ossia::find_if(old, [key](auto p) { return p && p->id().val() == key; });
505 Port* p{};
506 if(found != old.end())
507 {
508 p = *found;
509 *found = nullptr;
510 }
511 else
512 {
513 Ports added;
514 if constexpr(Input)
515 {
516 InletInitFunc<Info> make{*this, added};
517 make.inlet = key;
518 make(port, idx);
519 }
520 else
521 {
522 OutletInitFunc<Info> make{*this, added};
523 make.outlet = key;
524 make(port, idx);
525 }
526 SCORE_ASSERT(added.size() == 1);
527 p = added.front();
528 }
529 p->stableIdentity = true;
530 p->setName(QString::fromStdString(text));
531 next.push_back(p);
532 }
533 const bool changed = !std::equal(next.begin(), next.end(), first, first + count);
534 const auto offset = first - all.begin();
535 all.erase(first, first + count);
536 all.insert(all.begin() + offset, next.begin(), next.end());
537 count = rows.size();
538 ossia::small_pod_vector<Port*, 4> removed;
539 for(auto p : old)
540 if(p)
541 removed.push_back(p);
542 removeCablesOfPorts(removed);
543 if(changed)
544 {
545 if constexpr(Input)
546 inletsChanged();
547 else
548 outletsChanged();
549 }
550 for(auto p : removed)
551 delete p;
552 }
553
558 template <typename Port_T>
559 void removeCablesOfPorts(const ossia::small_pod_vector<Port_T*, 4>& ports)
560 {
561 if(ports.empty())
562 return;
563
564 // Not score::IDocument::documentFromObject: it throws when there is no
565 // document, and a process can be resized outside of one.
566 score::Document* doc{};
567 for(QObject* o = this->parent(); o && !doc; o = o->parent())
568 doc = qobject_cast<score::Document*>(o);
569 if(!doc)
570 return;
571
572 QObjectList objs;
573 objs.reserve(ports.size());
574 for(auto p : ports)
575 objs.push_back(p);
576
577 const auto& ctx = doc->context();
578 if(auto cables = Dataflow::saveCables(objs, ctx); !cables.empty())
579 Dataflow::removeCables(cables, ctx);
580 }
581
582 template <typename P, std::size_t N>
583 void request_new_dynamic_input_count(P& port, avnd::field_index<N> idx, int count)
584 {
585 const int current_model_ports = dynamic_ports.num_in_ports(idx);
586 if(current_model_ports == count || count < 0 || count > 512)
587 return;
588
589 ossia::small_pod_vector<Process::Inlet*, 4> to_delete;
590 if(current_model_ports < count)
591 {
592 // Add new ports
593 // 1. Find the location where to add them
594 auto res = avnd_input_idx_to_iterator(idx);
595 res += current_model_ports;
596 Process::Inlets inlets_to_add;
597 InletInitFunc<Info> inlets{*this, inlets_to_add};
598 inlets.inlet = 10000 + N * 1000 + current_model_ports;
599 int sz = 0;
600 for(int i = current_model_ports; i < count; i++)
601 {
602 inlets(port, idx);
603 if(std::ssize(inlets_to_add) > sz)
604 {
605 sz = std::ssize(inlets_to_add);
606 auto new_inlet = inlets_to_add.back();
607 if(auto nm = new_inlet->name(); nm.contains("{}"))
608 {
609 nm.replace("{}", QString::number(i));
610 new_inlet->setName(nm);
611 }
612 }
613 }
614 m_inlets.insert(res, inlets_to_add.begin(), inlets_to_add.end());
615 }
616 else if(current_model_ports > count)
617 {
618 // Delete the ports
619 auto res = avnd_input_idx_to_iterator(idx);
620 res += count;
621 auto begin_deleted = res;
622 for(int i = 0; i < (current_model_ports - count); i++)
623 {
624 to_delete.push_back(*res);
625 ++res;
626 }
627 m_inlets.erase(begin_deleted, res);
628 }
629
630 dynamic_ports.num_in_ports(idx) = count;
631
632 removeCablesOfPorts(to_delete);
633 inletsChanged();
634 for(auto port : to_delete)
635 delete port;
636 }
637
638 template <typename P, std::size_t N>
639 void request_new_dynamic_output_count(P& port, avnd::field_index<N> idx, int count)
640 {
641 const int current_model_ports = dynamic_ports.num_out_ports(idx);
642 if(current_model_ports == count || count < 0 || count > 512)
643 return;
644
645 ossia::small_pod_vector<Process::Outlet*, 4> to_delete;
646 if(current_model_ports < count)
647 {
648 // Add new ports
649 // 1. Find the location where to add them
650 auto res = avnd_output_idx_to_iterator(idx);
651 res += current_model_ports;
652 Process::Outlets outlets_to_add;
653 OutletInitFunc<Info> outlets{*this, outlets_to_add};
654 outlets.outlet = 1000000 + N * 1000 + current_model_ports;
655 int sz = 0;
656 for(int i = current_model_ports; i < count; i++)
657 {
658 outlets(port, idx);
659 if(std::ssize(outlets_to_add) > sz)
660 {
661 sz = std::ssize(outlets_to_add);
662 auto new_outlet = outlets_to_add.back();
663 if(auto nm = new_outlet->name(); nm.contains("{}"))
664 {
665 nm.replace("{}", QString::number(i));
666 new_outlet->setName(nm);
667 }
668 }
669 }
670 m_outlets.insert(res, outlets_to_add.begin(), outlets_to_add.end());
671 }
672 else if(current_model_ports > count)
673 {
674 // Delete the ports
675 auto res = avnd_output_idx_to_iterator(idx);
676 res += count;
677 auto begin_deleted = res;
678 for(int i = 0; i < (current_model_ports - count); i++)
679 {
680 to_delete.push_back(*res);
681 ++res;
682 }
683 m_outlets.erase(begin_deleted, res);
684 }
685
686 dynamic_ports.num_out_ports(idx) = count;
687
688 removeCablesOfPorts(to_delete);
689 outletsChanged();
690 for(auto port : to_delete)
691 delete port;
692 }
693
694 void init_all_ports()
695 {
696 InletInitFunc<Info> inlets{*this, m_inlets};
697 OutletInitFunc<Info> outlets{*this, m_outlets};
698 avnd::port_visit_dispatcher<Info>([&inlets]<typename P>(P&& port, auto idx) {
699 if constexpr(!avnd::dynamic_ports_port<P>)
700 inlets(port, idx);
701 }, [&outlets]<typename P>(P&& port, auto idx) {
702 if constexpr(!avnd::dynamic_ports_port<P>)
703 outlets(port, idx);
704 });
705
706 if(!requires { Info::ossia_show_ports_by_default; })
707 if constexpr(oscr::has_ossia_layer<Info>)
708 hideAllInlets(*this);
709 }
710
711public:
712 int expected_input_ports() const noexcept
713 {
714 int count = 0;
715
716 // We have to adjust before accessing a port as there is the first "fake"
717 // port if the processor takes audio by argument
718 if constexpr(avnd::audio_argument_processor<Info>)
719 count += 1;
720 else if constexpr(avnd::tag_cv<Info>)
721 count += 1;
722
723 // The "messages" ports also go before
724 count += avnd::messages_introspection<Info>::size;
725
726 avnd::input_introspection<Info>::for_all([this, &count]<std::size_t Idx, typename P>(
727 avnd::field_reflection<Idx, P> field) {
728 int num_ports = 1;
729 if constexpr(avnd::dynamic_ports_port<P>)
730 num_ports = dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
731 count += num_ports;
732 });
733
734 return count;
735 }
736
737 int expected_output_ports() const noexcept
738 {
739 int count = 0;
740
741 // We have to adjust before accessing a port as there is the first "fake"
742 // port if the processor takes audio by argument
743 if constexpr(avnd::audio_argument_processor<Info>)
744 count += 1;
745 else if constexpr(avnd::tag_cv<Info>)
746 {
747 using operator_ret = typename avnd::function_reflection_o<Info>::return_type;
748 if constexpr(!std::is_void_v<operator_ret>)
749 count += 1;
750 }
751
752 avnd::output_introspection<Info>::for_all(
753 [this,
754 &count]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
755 int num_ports = 1;
756 if constexpr(avnd::dynamic_ports_port<P>)
757 num_ports = dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
758 count += num_ports;
759 });
760
761 return count;
762 }
763
764 std::span<Process::Inlet*> avnd_input_idx_to_model_ports(int index) const noexcept
765 {
766 int model_index = 0;
767
768 // We have to adjust before accessing a port as there is the first "fake"
769 // port if the processor takes audio by argument
770 if constexpr(avnd::audio_argument_processor<Info>)
771 model_index += 1;
772 else if constexpr(avnd::tag_cv<Info>)
773 model_index += 1;
774
775 // The "messages" ports also go before
776 model_index += avnd::messages_introspection<Info>::size;
777
778 std::span<Process::Inlet*> ret;
779 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size == 0)
780 {
781 ret = std::span<Process::Inlet*>(
782 const_cast<Process::Inlet**>(this->m_inlets.data()) + model_index + index, 1);
783 }
784 else
785 {
786 avnd::input_introspection<Info>::for_all(
787 [this, index, &model_index,
788 &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
789 if(Idx == index)
790 {
791 int num_ports = 1;
792 if constexpr(avnd::dynamic_ports_port<P>)
793 {
794 num_ports = dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
795 if(num_ports == 0)
796 {
797 ret = {};
798 return;
799 }
800 }
801 ret = std::span<Process::Inlet*>(
802 const_cast<Process::Inlet**>(this->m_inlets.data()) + model_index,
803 num_ports);
804 }
805 else
806 {
807 if constexpr(avnd::dynamic_ports_port<P>)
808 {
809 model_index += dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
810 }
811 else
812 {
813 model_index += 1;
814 }
815 }
816 });
817 }
818
819 return ret;
820 }
821
822 std::span<Process::Outlet*> avnd_output_idx_to_model_ports(int index) const noexcept
823 {
824 int model_index = 0;
825
826 // We have to adjust before accessing a port as there is the first "fake"
827 // port if the processor takes audio by argument
828 if constexpr(avnd::audio_argument_processor<Info>)
829 model_index += 1;
830 else if constexpr(avnd::tag_cv<Info>)
831 {
832 using operator_ret = typename avnd::function_reflection_o<Info>::return_type;
833 if constexpr(!std::is_void_v<operator_ret>)
834 model_index += 1;
835 }
836
837 // The "messages" ports also go before
838 model_index += avnd::messages_introspection<Info>::size;
839
840 std::span<Process::Outlet*> ret;
841 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size == 0)
842 {
843 ret = std::span<Process::Outlet*>(
844 const_cast<Process::Outlet**>(this->m_outlets.data()) + model_index + index,
845 1);
846 }
847 else
848 {
849 avnd::output_introspection<Info>::for_all(
850 [this, index, &model_index,
851 &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
852 if(Idx == index)
853 {
854 int num_ports = 1;
855 if constexpr(avnd::dynamic_ports_port<P>)
856 {
857 num_ports = dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
858 if(num_ports == 0)
859 {
860 ret = {};
861 return;
862 }
863 }
864 ret = std::span<Process::Outlet*>(
865 const_cast<Process::Outlet**>(this->m_outlets.data()) + model_index,
866 num_ports);
867 }
868 else
869 {
870 if constexpr(avnd::dynamic_ports_port<P>)
871 {
872 model_index += dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
873 }
874 else
875 {
876 model_index += 1;
877 }
878 }
879 });
880 }
881
882 return ret;
883 }
884
885 Process::Inlets::iterator avnd_input_idx_to_iterator(int index) const noexcept
886 {
887 int model_index = 0;
888
889 // We have to adjust before accessing a port as there is the first "fake"
890 // port if the processor takes audio by argument
891 // (same adjustment as avnd_input_idx_to_model_ports)
892 if constexpr(avnd::audio_argument_processor<Info>)
893 model_index += 1;
894 else if constexpr(avnd::tag_cv<Info>)
895 model_index += 1;
896
897 // The "messages" ports also go before
898 model_index += avnd::messages_introspection<Info>::size;
899
900 Process::Inlets::iterator ret;
901 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size == 0)
902 {
903 ret = const_cast<ProcessModel*>(this)->m_inlets.begin() + model_index;
904 }
905 else
906 {
907 avnd::input_introspection<Info>::for_all(
908 [this, index, &model_index,
909 &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
910 if(Idx == index)
911 {
912 ret = const_cast<ProcessModel*>(this)->m_inlets.begin() + model_index;
913 }
914 else
915 {
916 if constexpr(avnd::dynamic_ports_port<P>)
917 {
918 model_index += dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
919 }
920 else
921 {
922 model_index += 1;
923 }
924 }
925 });
926 }
927 return ret;
928 }
929
930 Process::Outlets::iterator avnd_output_idx_to_iterator(int index) const noexcept
931 {
932 int model_index = 0;
933
934 // We have to adjust before accessing a port as there is the first "fake"
935 // port if the processor takes audio by argument
936 // (same adjustment as avnd_output_idx_to_model_ports: no message outlets,
937 // and a cv processor only has an output if it returns something)
938 if constexpr(avnd::audio_argument_processor<Info>)
939 model_index += 1;
940 else if constexpr(avnd::tag_cv<Info>)
941 {
942 using operator_ret = typename avnd::function_reflection_o<Info>::return_type;
943 if constexpr(!std::is_void_v<operator_ret>)
944 model_index += 1;
945 }
946
947 Process::Outlets::iterator ret;
948 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size == 0)
949 {
950 ret = const_cast<ProcessModel*>(this)->m_outlets.begin() + model_index;
951 }
952 else
953 {
954 avnd::output_introspection<Info>::for_all(
955 [this, index, &model_index,
956 &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
957 if(Idx == index)
958 {
959 ret = const_cast<ProcessModel*>(this)->m_outlets.begin() + model_index;
960 }
961 else
962 {
963 if constexpr(avnd::dynamic_ports_port<P>)
964 {
965 model_index += dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
966 }
967 else
968 {
969 model_index += 1;
970 }
971 }
972 });
973 }
974 return ret;
975 }
976};
977}
978
979template <typename Info>
980struct is_custom_serialized<oscr::ProcessModel<Info>> : std::true_type
981{
982};
983
984template <typename Info>
985struct TSerializer<DataStream, oscr::ProcessModel<Info>>
986{
988 static void readFrom(DataStream::Serializer& s, const model_type& obj)
989 {
990 Process::readPorts(s, obj.m_inlets, obj.m_outlets);
991
992 // Save the recorded amount of dynamic ports for each port
993 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
994 {
995 avnd::dynamic_ports_input_introspection<Info>::for_all(
996 [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
997 if constexpr(avnd::dynamic_ports_port<P>)
998 s.stream() << obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
999 });
1000 }
1001 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1002 {
1003 avnd::dynamic_ports_output_introspection<Info>::for_all(
1004 [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1005 if constexpr(avnd::dynamic_ports_port<P>)
1006 s.stream() << obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1007 });
1008 }
1009
1010 s.insertDelimiter();
1011 }
1012
1013 static void writeTo(DataStream::Deserializer& s, model_type& obj)
1014 {
1015 Process::writePorts(
1016 s, s.components.interfaces<Process::PortFactoryList>(), obj.m_inlets,
1017 obj.m_outlets, &obj);
1018
1019 // Read the recorded amount of dynamic ports for each port
1020 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1021 {
1022 avnd::dynamic_ports_input_introspection<Info>::for_all(
1023 [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1024 if constexpr(avnd::dynamic_ports_port<P>)
1025 s.stream() >> obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1026 });
1027 }
1028 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1029 {
1030 avnd::dynamic_ports_output_introspection<Info>::for_all(
1031 [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1032 if constexpr(avnd::dynamic_ports_port<P>)
1033 s.stream() >> obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1034 });
1035 }
1036 s.checkDelimiter();
1037 }
1038};
1039
1040template <typename Info>
1041struct TSerializer<JSONObject, oscr::ProcessModel<Info>>
1042{
1044 static void readFrom(JSONObject::Serializer& s, const model_type& obj)
1045 {
1046 Process::readPorts(s, obj.m_inlets, obj.m_outlets);
1047 // Save the recorded amount of dynamic ports for each port
1048 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1049 {
1050 ossia::string_map<int> indices;
1051 avnd::dynamic_ports_input_introspection<Info>::for_all(
1052 [&obj,
1053 &indices]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1054 if constexpr(avnd::dynamic_ports_port<P>)
1055 {
1056 indices[std::string(avnd::get_c_identifier<P>())]
1057 = obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1058 }
1059 });
1060 s.obj["DynamicInlets"] = indices;
1061 }
1062 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1063 {
1064 ossia::string_map<int> indices;
1065 avnd::dynamic_ports_output_introspection<Info>::for_all(
1066 [&obj,
1067 &indices]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1068 if constexpr(avnd::dynamic_ports_port<P>)
1069 indices[std::string(avnd::get_c_identifier<P>())]
1070 = obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1071 });
1072 s.obj["DynamicOutlets"] = indices;
1073 }
1074 }
1075
1076 static void writeTo(JSONObject::Deserializer& s, model_type& obj)
1077 {
1078 Process::writePorts(
1079 s, s.components.interfaces<Process::PortFactoryList>(), obj.m_inlets,
1080 obj.m_outlets, &obj);
1081 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1082 {
1083 if(auto val = s.obj.tryGet("DynamicInlets"))
1084 {
1085 static ossia::string_map<int> indices;
1086 indices.clear();
1087 indices <<= *val;
1088 avnd::dynamic_ports_input_introspection<Info>::for_all(
1089 [&obj]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1090 if constexpr(avnd::dynamic_ports_port<P>)
1091 obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{})
1092 = indices[std::string(avnd::get_c_identifier<P>())];
1093 });
1094 }
1095 }
1096 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1097 {
1098 if(auto val = s.obj.tryGet("DynamicOutlets"))
1099 {
1100 static ossia::string_map<int> indices;
1101 indices.clear();
1102 indices <<= *val;
1103 avnd::dynamic_ports_output_introspection<Info>::for_all(
1104 [&obj]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1105 if constexpr(avnd::dynamic_ports_port<P>)
1106 obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{})
1107 = indices[std::string(avnd::get_c_identifier<P>())];
1108 });
1109 }
1110 }
1111 }
1112};
Definition VisitorInterface.hpp:53
Definition DataStreamVisitor.hpp:27
Definition DataStreamVisitor.hpp:202
Definition VisitorInterface.hpp:61
Definition JSONVisitor.hpp:52
Definition JSONVisitor.hpp:423
Definition Port.hpp:218
Definition Port.hpp:192
Definition Port.hpp:300
Definition PortFactory.hpp:82
The Process class.
Definition score-lib-process/Process/Process.hpp:75
The id_base_t class.
Definition Identifier.hpp:59
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:88
The Document class is the central part of the software.
Definition Document.hpp:51
Definition Metadatas.hpp:93
Definition Concepts.hpp:49
@ Input
Read by the process: must be collected next to the document.
Definition Controls.hpp:27
QString locateFilePath(const QString &filename) noexcept
Definition File.cpp:84
Definition CableHelpers.hpp:88
Static metadata implementation.
Definition lib/score/tools/Metadata.hpp:36
Definition PortForward.hpp:23
Definition PortForward.hpp:27
Definition VisitorInterface.hpp:13
Definition TimeValue.hpp:21
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:52
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:47