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 <QPointer>
26#include <QFileInfo>
27#include <QTimer>
28
29#include <avnd/binding/ossia/data_node.hpp>
30#include <avnd/binding/ossia/dynamic_ports.hpp>
31#include <avnd/binding/ossia/time_controls.hpp>
32#include <avnd/common/for_nth.hpp>
33#include <avnd/concepts/gfx.hpp>
34#include <avnd/concepts/ui.hpp>
35#include <avnd/introspection/messages.hpp>
36#include <avnd/wrappers/bus_host_process_adapter.hpp>
37
38#include <score_plugin_engine.hpp>
39
44namespace oscr
45{
46
47template <typename Info>
49{
50};
51
52template <typename Info>
56
57namespace
58{
59struct dummy_ui_callback
60{
61 void operator()(const QByteArray& arr) noexcept { }
62};
63}
64
65template <avnd::has_processor_to_gui_bus Info>
67{
71 std::vector<std::pair<QPointer<QObject>, std::function<void(QByteArray)>>>
73
74 void add_to_ui_listener(QObject* view, std::function<void(QByteArray)> f)
75 {
76 std::erase_if(to_ui_listeners, [](const auto& l) { return l.first.isNull(); });
77 to_ui_listeners.emplace_back(view, std::move(f));
78 }
79
80 std::function<void(QByteArray)> to_ui = [this](QByteArray mess) {
81 std::erase_if(to_ui_listeners, [](const auto& l) { return l.first.isNull(); });
82 const auto n = to_ui_listeners.size();
83 for(std::size_t i = 0; i < n; i++)
84 {
85 // A listener that adds a view also drops the views gone meanwhile
86 if(i >= to_ui_listeners.size())
87 break;
88 // The listener may add another view: hold a copy of its callback.
89 auto f = to_ui_listeners[i].second;
90 if(i + 1 == n)
91 f(std::move(mess));
92 else
93 f(mess);
94 }
95 };
96
97 MessageBusWrapperToUi() = default;
98 MessageBusWrapperToUi(const MessageBusWrapperToUi&) = delete;
99 MessageBusWrapperToUi& operator=(const MessageBusWrapperToUi&) = delete;
100};
101
102template <avnd::has_gui_to_processor_bus Info>
104{
105 std::function<void(QByteArray)> from_ui = dummy_ui_callback{};
106};
107
109inline void hideAllInlets(Process::ProcessModel& proc, bool outlets)
110{
111 for(auto& p : proc.inlets())
112 p->displayHandledExplicitly = true;
113 if(outlets)
114 for(auto& p : proc.outlets())
115 p->displayHandledExplicitly = true;
116}
117
118template <typename Info>
119class ProcessModel final
120 : public Process::ProcessModel
121 , public MessageBusWrapperFromUi<Info>
122 , public MessageBusWrapperToUi<Info>
123{
124 SCORE_SERIALIZE_FRIENDS
125 PROCESS_METADATA_IMPL(ProcessModel<Info>)
126 friend struct TSerializer<DataStream, oscr::ProcessModel<Info>>;
127 friend struct TSerializer<JSONObject, oscr::ProcessModel<Info>>;
128
129public:
130 [[no_unique_address]]
131 oscr::dynamic_ports_storage<Info> dynamic_ports;
132
133 [[no_unique_address]]
134 ossia::type_if<Info, oscr::has_ports_callbacks<Info>>
135 object_storage_for_ports_callbacks;
136
138 const TimeVal& duration, const Id<Process::ProcessModel>& id,
139 const score::DocumentContext& ctx, QObject* parent)
141 duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
142 {
143 metadata().setInstanceName(*this);
144
145 init_common();
146 init_before_port_creation();
147 init_all_ports();
148 init_after_port_creation();
149 }
150
152 const TimeVal& duration, const QString& custom,
153 const Id<Process::ProcessModel>& id, QObject* parent)
155 duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
156 {
157 metadata().setInstanceName(*this);
158
159 init_common();
160 init_before_port_creation();
161 init_all_ports();
162
163 if constexpr(avnd::file_input_introspection<Info>::size > 0)
164 {
165 static constexpr auto idx
166 = avnd::file_input_introspection<Info>::index_to_field_index(0);
167 setupInitialStringPort(idx, custom);
168 }
169 else if constexpr(avnd::control_input_introspection<Info>::size > 0)
170 {
171 static constexpr auto idx
172 = avnd::control_input_introspection<Info>::index_to_field_index(0);
173 using type =
174 typename avnd::control_input_introspection<Info>::template nth_element<0>;
175 if constexpr(avnd::string_ish<decltype(type::value)>)
176 setupInitialStringPort(idx, custom);
177 }
178 init_after_port_creation();
179 }
180
181 void setupInitialStringPort(int idx, const QString& custom) noexcept
182 {
183 Process::Inlet* port = avnd_input_idx_to_model_ports(idx)[0];
184 auto pp = safe_cast<Process::ControlInlet*>(port);
185
186 if(auto val = pp->value(); bool(val.target<std::string>()))
187 {
188 pp->setValue(custom.toStdString());
189 }
190 }
191
192 template <typename Impl>
193 explicit ProcessModel(Impl& vis, QObject* parent)
194 : Process::ProcessModel{vis, parent}
195 {
196 init_common();
197 init_before_port_creation();
198 vis.writeTo(*this);
199 check_all_ports();
200 // What is hidden follows the object, not what was saved
201 if constexpr(oscr::has_ossia_layer<Info> && avnd::tag_fully_custom_item<Info>)
202 if(!requires { Info::ossia_show_ports_by_default; })
203 for(auto& p : this->outlets())
204 p->displayHandledExplicitly = false;
205 upgrade_value_inlets_to_controls();
206 upgrade_controls_to_value_inlets();
207 upgrade_changed_control_types();
208 init_after_port_creation();
209 }
210
211 ~ProcessModel() override { }
212
224 void loadPreset(const Process::Preset& preset) override
225 {
226 const rapidjson::Document doc = readJson(preset.data);
227 if(!doc.IsArray())
228 return;
229 for(const auto& arr : doc.GetArray())
230 {
231 if(!arr.IsArray() || arr.Size() < 2 || !arr[0].IsInt())
232 continue;
233 Process::ControlInlet* ctrl{};
234 if(arr.Size() >= 3 && arr[2].IsString())
235 ctrl = preset_control(
236 arr[0].GetInt(),
237 QString::fromUtf8(arr[2].GetString(), qsizetype(arr[2].GetStringLength())));
238 else
239 ctrl = unnamed_preset_control(arr[0].GetInt());
240 if(!ctrl)
241 continue;
242 ossia::value val = JsonValue{arr[1]}.to<ossia::value>();
243 upgrade_preset_value(*ctrl, val);
244 ctrl->setValue(val);
245 }
246 }
247
249 Process::Preset savePreset() const noexcept override
250 {
252 p.name = this->metadata().getName();
253 p.key.key = this->concreteKey();
254
255 JSONReader r;
256 r.stream.StartArray();
257 for(const auto& inlet : m_inlets)
258 {
259 if(auto ctrl = qobject_cast<Process::ControlInlet*>(inlet))
260 {
261 r.stream.StartArray();
262 r.stream.Int(ctrl->id().val());
263 r.readFrom(ctrl->value());
264 const QByteArray name = ctrl->name().toUtf8();
265 r.stream.String(name.data(), rapidjson::SizeType(name.size()));
266 r.stream.EndArray();
267 }
268 }
269 r.stream.EndArray();
270 p.data = r.toByteArray();
271 return p;
272 }
273
274private:
275 Process::ControlInlet* control_named(const QString& name, int id) const noexcept
276 {
278 for(auto* in : m_inlets)
279 {
280 if(in->name() != name)
281 continue;
282 if(auto ctrl = qobject_cast<Process::ControlInlet*>(in))
283 {
284 if(ctrl->id().val() == id)
285 return ctrl;
286 if(!res)
287 res = ctrl;
288 }
289 }
290 return res;
291 }
292
293 Process::ControlInlet* preset_control(int id, const QString& name) const noexcept
294 {
295 if(auto ctrl = control_named(name, id))
296 return ctrl;
298 avnd::input_introspection<Info>::for_all(
299 [&]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
300 if constexpr(requires { P::former_name(); } && requires { P::name(); })
301 {
302 const auto was = P::former_name();
303 if(res || name != QString::fromUtf8(was.data(), qsizetype(was.size())))
304 return;
305 const auto now = P::name();
306 res = control_named(QString::fromUtf8(now.data(), qsizetype(now.size())), id);
307 }
308 });
309 return res;
310 }
311
312 Process::ControlInlet* unnamed_preset_control(int id) const noexcept
313 {
314 if constexpr(requires { Info::removed_inlets(); })
315 {
316 // The ports of the process are where the spec declares them, whatever
317 // their ids in this document.
318 static constexpr auto removed = Info::removed_inlets();
319 int position = id;
320 for(int r : removed)
321 {
322 if(r == id)
323 return nullptr;
324 if(r < id)
325 position--;
326 }
327 if(position < 0 || position >= std::ssize(m_inlets))
328 return nullptr;
329 return qobject_cast<Process::ControlInlet*>(m_inlets[position]);
330 }
331 else
332 {
333 return qobject_cast<Process::ControlInlet*>(
334 this->inlet(Id<Process::Port>{id}));
335 }
336 }
337
340 template <typename P>
341 static std::optional<std::string> current_choice(const std::string& saved)
342 {
343 static constexpr auto range = avnd::get_range<P>();
344 const auto names = avnd::to_enum_range(range.values);
345 if(ossia::any_of(names, [&](const auto& n) { return n == saved; }))
346 return std::nullopt;
347 const auto former = P::former_values();
348 for(std::size_t i = 0; i < former.size() && i < names.size(); i++)
349 if(former[i] == saved)
350 return std::string(names[i]);
351 return std::nullopt;
352 }
353
354 void upgrade_preset_value(const Process::ControlInlet& ctrl, ossia::value& val)
355 {
356 avnd::input_introspection<Info>::for_all(
357 [&]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
358 if constexpr(requires { P::former_values(); })
359 {
360 auto ports = avnd_input_idx_to_model_ports(Idx);
361 if(ports.size() == 1 && ports[0] == &ctrl)
362 if(const auto* str = val.target<std::string>())
363 if(auto now = current_choice<P>(*str))
364 val = std::move(*now);
365 }
366 });
367 if(val.get_type() == ctrl.value().get_type())
368 return;
369 avnd::input_introspection<Info>::for_all(
370 [&]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
371 if constexpr(requires(const ossia::value& v) { P::upgrade_value(v); })
372 {
373 auto ports = avnd_input_idx_to_model_ports(Idx);
374 if(ports.size() == 1 && ports[0] == &ctrl)
375 val = P::upgrade_value(std::as_const(val));
376 }
377 else if constexpr(requires(float f) {
378 { P::upgrade_value(f) } -> std::convertible_to<float>;
379 })
380 {
381 auto ports = avnd_input_idx_to_model_ports(Idx);
382 if(ports.size() == 1 && ports[0] == &ctrl && is_number(val))
383 if(auto v = carried_value(
384 float(P::upgrade_value(ossia::convert<float>(val))), ctrl))
385 val = std::move(*v);
386 }
387 });
388 }
389
390 void init_common()
391 {
392 if constexpr(avnd::tag_loops_by_default<Info>)
393 setLoops(true);
394 }
395
396 void init_before_port_creation() { init_dynamic_ports(); }
397 void init_after_port_creation()
398 {
399 init_controller_ports();
400 init_folder_comboboxes();
401 init_single_cable_ports();
402 init_time_choosers();
403 }
404
406 void init_time_choosers()
407 {
408 avnd::input_introspection<Info>::for_all(
409 [this]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
410 if constexpr(oscr::offers_bar_sync<P>)
411 for(auto* port : avnd_input_idx_to_model_ports(Idx))
412 if(auto* tc = qobject_cast<Process::TimeChooser*>(port))
413 tc->offersBarSync = true;
414 });
415 }
416
417 void init_single_cable_ports()
418 {
419 avnd::input_introspection<Info>::for_all(
420 [this]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
421 if constexpr(single_cable_port<P>)
422 for(auto* port : avnd_input_idx_to_model_ports(Idx))
423 port->singleCable = true;
424 });
425 }
426
427 // Folder-backed comboboxes (halp::folder_combobox): each carries the name of
428 // a sibling folder port; list that folder's files as the combobox items, at
429 // edit/load time, and refresh whenever the folder value changes. Runs after
430 // deserialization, so a loaded document's folder value is already in place.
431 void init_folder_comboboxes()
432 {
433 for(auto* inl : m_inlets)
434 {
435 auto combo = qobject_cast<Process::ComboBox*>(inl);
436 if(!combo || combo->folderPortName.isEmpty())
437 continue;
438
439 Process::ControlInlet* folderPort = nullptr;
440 for(auto* other : m_inlets)
441 {
442 auto ci = qobject_cast<Process::ControlInlet*>(other);
443 if(ci && ci != combo && ci->name() == combo->folderPortName)
444 {
445 folderPort = ci;
446 break;
447 }
448 }
449
450 auto pathOf = [this](Process::ControlInlet* p) -> QString {
451 if(!p)
452 return {};
453 if(auto s = p->value().target<std::string>())
454 {
455 // Stored paths are templates (<PROJECT>:..., <LIBRARY>:..., or
456 // document-relative); resolve before touching the filesystem, or a
457 // project-relative folder lists nothing.
458 auto path = QString::fromStdString(*s);
459 if(path.isEmpty())
460 return {};
461 // Walk up rather than score::IDocument::documentFromObject, which
462 // throws when there is no document -- ports are built before the
463 // process is attached to one.
464 score::Document* doc{};
465 for(QObject* o = this->parent(); o && !doc; o = o->parent())
466 doc = qobject_cast<score::Document*>(o);
467 if(doc)
468 path = score::locateFilePath(path, doc->context());
469 // The sibling port may name a folder, or a file (e.g. a sound-file
470 // port): in the latter case list the file's containing folder.
471 if(QFileInfo fi{path}; fi.isFile())
472 return fi.absolutePath();
473 return path;
474 }
475 return {};
476 };
477
478 combo->repopulateFromFolder(pathOf(folderPort));
479 if(folderPort)
480 QObject::connect(
481 folderPort, &Process::ControlInlet::valueChanged, combo,
482 [combo, folderPort, pathOf](const ossia::value&) {
483 combo->repopulateFromFolder(pathOf(folderPort));
484 });
485 }
486 }
487 void check_all_ports()
488 {
489 if(std::ssize(m_inlets) == expected_input_ports()
490 && std::ssize(m_outlets) == expected_output_ports())
491 {
492 rename_former_kept_ports();
493 upgrade_former_values();
494 return;
495 }
496
497 std::vector<Dataflow::SavedPort> oldInlets, oldOutlets;
498 std::vector<saved_port> oldInletInfo, oldOutletInfo;
499 for(auto& port : m_inlets)
500 {
501 oldInlets.emplace_back(
502 Dataflow::SavedPort{port->name(), port->type(), port->saveData()});
503 oldInletInfo.push_back(saved_port::of(*port));
504 }
505 for(auto& port : m_outlets)
506 {
507 oldOutlets.emplace_back(
508 Dataflow::SavedPort{port->name(), port->type(), port->saveData()});
509 oldOutletInfo.push_back(saved_port::of(*port));
510 }
511
512 qDeleteAll(m_inlets);
513 m_inlets.clear();
514 qDeleteAll(m_outlets);
515 m_outlets.clear();
516
517 init_all_ports();
518 init_dynamic_port_instances();
519
520 rename_former_ports(oldInlets);
521
522 // A port the spec gained since the save just comes back at its default:
523 // only a saved port that matched nothing actually lost its value.
524 std::vector<int> inletMatches, outletMatches;
525 const auto lost = Dataflow::reloadPortsInNewProcess(
526 oldInlets, oldOutlets, *this, &inletMatches, &outletMatches);
527 keep_saved_ids(m_inlets, inletMatches, oldInletInfo);
528 keep_saved_ids(m_outlets, outletMatches, oldOutletInfo);
529 upgrade_rebuilt_control_kinds(inletMatches, oldInletInfo);
530 upgrade_former_values();
531 if(!lost.empty())
532 {
533 const std::string_view type_name = avnd::get_name<Info>();
534 qWarning() << QLatin1String(type_name.data(), type_name.size())
535 << this->metadata().getName()
536 << ": saved ports lost on load (renamed or removed since):"
537 << lost.join(", ");
538 }
539 }
540
542 struct saved_port
543 {
544 int id{};
547 std::optional<ossia::value> value;
548 bool stable{};
549
550 static saved_port of(const Process::Port& port)
551 {
552 saved_port p{port.id().val(), port.concreteKey(), std::nullopt, port.stableIdentity};
553 if(auto ctrl = qobject_cast<const Process::ControlInlet*>(&port))
554 p.value = ctrl->value();
555 return p;
556 }
557 };
558
562 template <typename Ports>
563 static void keep_saved_ids(
564 Ports& ports, const std::vector<int>& matches, const std::vector<saved_port>& saved)
565 {
566 if(matches.size() != ports.size())
567 return;
568 std::vector<int> ids(ports.size());
569 std::vector<int> taken;
570 int next = 0;
571 for(std::size_t i = 0; i < ports.size(); i++)
572 {
573 next = std::max(next, ports[i]->id().val() + 1);
574 ids[i] = ports[i]->id().val();
575 if(matches[i] >= 0 && !ports[i]->stableIdentity && !saved[matches[i]].stable)
576 ids[i] = saved[matches[i]].id;
577 if(matches[i] >= 0 || ports[i]->stableIdentity)
578 taken.push_back(ids[i]);
579 }
580 for(const auto& p : saved)
581 next = std::max(next, p.id + 1);
582 for(std::size_t i = 0; i < ports.size(); i++)
583 {
584 if(matches[i] < 0 && !ports[i]->stableIdentity)
585 {
586 if(ossia::contains(taken, ids[i]))
587 ids[i] = next++;
588 taken.push_back(ids[i]);
589 }
590 if(ports[i]->id().val() != ids[i])
591 ports[i]->setId(Id<Process::Port>{ids[i]});
592 }
593 }
594
598 void upgrade_rebuilt_control_kinds(
599 const std::vector<int>& matches, const std::vector<saved_port>& saved)
600 {
601 avnd::input_introspection<Info>::for_all(
602 [&]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
603 if constexpr(
604 avnd::parameter_port<P> && !oscr::ossia_port<P>
605 && !avnd::dynamic_ports_port<P> && avnd::has_widget<P>
606 && !requires { P::on_controller_interaction(); })
607 {
608 auto ports = avnd_input_idx_to_model_ports(Idx);
609 if(ports.size() != 1)
610 return;
611 const auto pos = std::size_t(ossia::find(m_inlets, ports[0]) - m_inlets.begin());
612 if(pos >= matches.size() || matches[pos] < 0)
613 return;
614 const auto& old = saved[std::size_t(matches[pos])];
615 auto fresh = qobject_cast<Process::ControlInlet*>(ports[0]);
616 if(!fresh || !old.value || old.key == fresh->concreteKey())
617 return;
618 if(auto v = upgraded_value<P>(*old.value, *fresh))
619 fresh->setValue(*v);
620 }
621 });
622 }
623
629 void rename_former_ports(std::vector<Dataflow::SavedPort>& saved)
630 {
631 struct rename
632 {
633 QString was, now;
634 bool applies{};
635 };
636 std::vector<rename> renames;
637 avnd::input_introspection<Info>::for_all(
638 [&]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
639 if constexpr(requires { P::former_name(); } && requires { P::name(); })
640 {
641 const auto now = P::name();
642 const auto was = P::former_name();
643 renames.push_back(
644 {QString::fromUtf8(was.data(), qsizetype(was.size())),
645 QString::fromUtf8(now.data(), qsizetype(now.size()))});
646 }
647 });
648 const auto was_saved = [&](const QString& name) {
649 return ossia::any_of(saved, [&](const auto& p) { return p.name == name; });
650 };
651 for(bool changed = true; changed;)
652 {
653 changed = false;
654 for(auto& r : renames)
655 {
656 if(r.applies || !was_saved(r.was))
657 continue;
658 const bool taken_by_now = was_saved(r.now)
659 && !ossia::any_of(renames, [&](const rename& o) {
660 return o.applies && o.was == r.now;
661 });
662 if(!taken_by_now)
663 changed = r.applies = true;
664 }
665 }
666 // All at once, from the names as they were saved.
667 std::vector<std::pair<std::size_t, QString>> moves;
668 for(const auto& r : renames)
669 {
670 if(!r.applies)
671 continue;
672 for(std::size_t i = 0; i < saved.size(); i++)
673 if(saved[i].name == r.was
674 && !ossia::any_of(moves, [i](const auto& m) { return m.first == i; }))
675 {
676 moves.emplace_back(i, r.now);
677 break;
678 }
679 }
680 for(auto& [i, name] : moves)
681 saved[i].name = name;
682 }
683
686 void rename_former_kept_ports()
687 {
688 avnd::input_introspection<Info>::for_all(
689 [this]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
690 if constexpr(requires { P::former_name(); } && requires { P::name(); })
691 {
692 auto ports = avnd_input_idx_to_model_ports(Idx);
693 if(ports.size() != 1)
694 return;
695 const auto was = P::former_name();
696 if(ports[0]->name() != QString::fromUtf8(was.data(), qsizetype(was.size())))
697 return;
698 const auto now = P::name();
699 ports[0]->setName(QString::fromUtf8(now.data(), qsizetype(now.size())));
700 }
701 });
702 }
703
707 void upgrade_former_values()
708 {
709 avnd::input_introspection<Info>::for_all(
710 [this]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
711 if constexpr(requires { P::former_values(); })
712 {
713 auto ports = avnd_input_idx_to_model_ports(Idx);
714 if(ports.size() != 1)
715 return;
716 auto inlet = qobject_cast<Process::ControlInlet*>(ports[0]);
717 if(!inlet)
718 return;
719 const ossia::value& value = inlet->value();
720 const auto* str = value.template target<std::string>();
721 if(!str)
722 return;
723 if(auto now = current_choice<P>(*str))
724 inlet->setValue(std::move(*now));
725 }
726 });
727 }
728
736 template <typename F, std::size_t Idx>
737 Process::ControlInlet* upgradeControllerPort(F& field, avnd::field_index<Idx> idx)
738 {
739 auto ports = avnd_input_idx_to_model_ports(Idx);
740 SCORE_ASSERT(ports.size() == 1);
741 auto old_inlet = qobject_cast<Process::ControlInlet*>(ports[0]);
742 SCORE_ASSERT(old_inlet);
743
744 Process::Inlets fresh;
745 InletInitFunc<Info> make{*this, fresh};
746 make.inlet = old_inlet->id().val();
747 make(field, idx);
748 if(fresh.size() != 1)
749 {
750 qDeleteAll(fresh);
751 return old_inlet;
752 }
753
754 auto new_inlet = qobject_cast<Process::ControlInlet*>(fresh[0]);
755 if(!new_inlet || new_inlet->concreteKey() == old_inlet->concreteKey())
756 {
757 qDeleteAll(fresh);
758 return old_inlet;
759 }
760
761 new_inlet->loadData(old_inlet->saveData(), Process::PortLoadDataFlags::ReloadValue);
762 new_inlet->setExposed(old_inlet->exposed());
763 new_inlet->setDescription(old_inlet->description());
764 new_inlet->displayHandledExplicitly = old_inlet->displayHandledExplicitly;
765
766 auto it = ossia::find(m_inlets, old_inlet);
767 SCORE_ASSERT(it != m_inlets.end());
768 *it = new_inlet;
769 delete old_inlet;
770 return new_inlet;
771 }
772
778 static bool is_number(const ossia::value& v) noexcept
779 {
780 const auto t = v.get_type();
781 return t == ossia::val_type::FLOAT || t == ossia::val_type::INT
782 || t == ossia::val_type::BOOL;
783 }
784
785 static std::optional<ossia::value>
786 carried_value(const ossia::value& old, const Process::ControlInlet& fresh)
787 {
788 const auto from = old.get_type();
789 const auto to = fresh.value().get_type();
790 if(from == to)
791 return old;
792 if(!is_number(old))
793 return std::nullopt;
794 if(qobject_cast<const Process::TimeChooser*>(&fresh))
795 return ossia::vec2f{ossia::convert<float>(old), 0.f};
796 switch(to)
797 {
798 case ossia::val_type::FLOAT:
799 return ossia::convert<float>(old);
800 case ossia::val_type::INT:
801 return int(std::lround(ossia::convert<float>(old)));
802 case ossia::val_type::BOOL:
803 return ossia::convert<bool>(old);
804 default:
805 return std::nullopt;
806 }
807 }
808
811 template <typename P>
812 static std::optional<ossia::value>
813 upgraded_value(const ossia::value& old_value, const Process::ControlInlet& fresh)
814 {
815 if constexpr(requires(float f) {
816 { P::upgrade_value(f) } -> std::convertible_to<float>;
817 })
818 {
819 if(!is_number(old_value))
820 return std::nullopt;
821 return carried_value(float(P::upgrade_value(ossia::convert<float>(old_value))), fresh);
822 }
823 else if constexpr(requires { P::upgrade_value(old_value); })
824 return P::upgrade_value(old_value);
825 else
826 return carried_value(old_value, fresh);
827 }
828
835 void upgrade_changed_control_types()
836 {
837 avnd::input_introspection<Info>::for_all(
838 [this]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
839 if constexpr(
840 avnd::parameter_port<P> && !oscr::ossia_port<P>
841 && !avnd::dynamic_ports_port<P> && avnd::has_widget<P>
842 && std::is_default_constructible_v<P>
843 // Controllers have their own upgrade, which keeps their value
844 // (init_controller_ports).
845 && !requires { P::on_controller_interaction(); })
846 {
847 auto ports = avnd_input_idx_to_model_ports(Idx);
848 if(ports.size() != 1)
849 return;
850 auto old_inlet = qobject_cast<Process::ControlInlet*>(ports[0]);
851 if(!old_inlet)
852 return;
853
854 Process::Inlets fresh;
855 InletInitFunc<Info> make{*this, fresh};
856 make.inlet = old_inlet->id().val();
857 make(P{}, avnd::field_index<Idx>{});
858 auto new_inlet = fresh.size() == 1
859 ? qobject_cast<Process::ControlInlet*>(fresh[0])
860 : nullptr;
861 if(!new_inlet || new_inlet->concreteKey() == old_inlet->concreteKey())
862 {
863 qDeleteAll(fresh);
864 return;
865 }
866
867 new_inlet->setExposed(old_inlet->exposed());
868 new_inlet->setAddress(old_inlet->address());
869 if(auto v = upgraded_value<P>(old_inlet->value(), *new_inlet))
870 new_inlet->setValue(*v);
871 // The cables into it: the document's cables point at this id, and the
872 // port lists them too.
873 new_inlet->takeCables(std::move(*old_inlet));
874 auto it = ossia::find(m_inlets, old_inlet);
875 SCORE_ASSERT(it != m_inlets.end());
876 *it = new_inlet;
877 delete old_inlet;
878 }
879 });
880 }
881
885 void upgrade_controls_to_value_inlets()
886 {
887 avnd::input_introspection<Info>::for_all(
888 [this]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
889 if constexpr(
890 avnd::parameter_port<P> && !oscr::ossia_port<P>
891 && !avnd::dynamic_ports_port<P> && !avnd::has_widget<P>
892 && std::is_default_constructible_v<P>)
893 {
894 auto ports = avnd_input_idx_to_model_ports(Idx);
895 if(ports.size() != 1)
896 return;
897 auto old_inlet = qobject_cast<Process::ControlInlet*>(ports[0]);
898 if(!old_inlet)
899 return;
900
901 Process::Inlets fresh;
902 InletInitFunc<Info> make{*this, fresh};
903 make.inlet = old_inlet->id().val();
904 make(P{}, avnd::field_index<Idx>{});
905 auto new_inlet
906 = fresh.size() == 1 ? qobject_cast<Process::ValueInlet*>(fresh[0]) : nullptr;
907 if(!new_inlet)
908 {
909 qDeleteAll(fresh);
910 return;
911 }
912
913 new_inlet->setExposed(old_inlet->exposed());
914 new_inlet->setAddress(old_inlet->address());
915 new_inlet->takeCables(std::move(*old_inlet));
916 auto it = ossia::find(m_inlets, old_inlet);
917 SCORE_ASSERT(it != m_inlets.end());
918 *it = new_inlet;
919 delete old_inlet;
920 }
921 });
922 }
923
924 void upgrade_value_inlets_to_controls()
925 {
926 avnd::input_introspection<Info>::for_all(
927 [this]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P>) {
928 if constexpr(
929 avnd::parameter_port<P> && !oscr::ossia_port<P>
930 && !avnd::dynamic_ports_port<P> && avnd::has_widget<P>
931 && std::is_default_constructible_v<P>)
932 upgrade_value_inlet(P{}, avnd::field_index<Idx>{});
933 });
934 }
935
936 template <typename F, std::size_t Idx>
937 void upgrade_value_inlet(const F& field, avnd::field_index<Idx> idx)
938 {
939 auto ports = avnd_input_idx_to_model_ports(Idx);
940 if(ports.size() != 1)
941 return;
942 auto old_inlet = ports[0];
943 if(qobject_cast<Process::ControlInlet*>(old_inlet)
944 || !qobject_cast<Process::ValueInlet*>(old_inlet))
945 return;
946
947 Process::Inlets fresh;
948 InletInitFunc<Info> make{*this, fresh};
949 make.inlet = old_inlet->id().val();
950 make(field, idx);
951 auto new_inlet
952 = fresh.size() == 1 ? qobject_cast<Process::ControlInlet*>(fresh[0]) : nullptr;
953 if(!new_inlet)
954 {
955 qDeleteAll(fresh);
956 return;
957 }
958
959 new_inlet->loadData(old_inlet->saveData());
960 new_inlet->setExposed(old_inlet->exposed());
961
962 auto it = ossia::find(m_inlets, old_inlet);
963 SCORE_ASSERT(it != m_inlets.end());
964 *it = new_inlet;
965 delete old_inlet;
966 }
967
968 void init_controller_ports()
969 {
971 {
972 avnd::control_input_introspection<Info>::for_all_n2(
973 avnd::get_inputs<Info>((Info&)this->object_storage_for_ports_callbacks),
974 [this]<std::size_t Idx, typename F>(
975 F& field, auto pred_index, avnd::field_index<Idx> idx) {
976 Info& obj = this->object_storage_for_ports_callbacks;
977 if constexpr(requires { F::on_controller_interaction(); })
978 {
979 upgradeControllerPort(field, idx);
980 // Version migration for controls whose old document representation
981 // differs from their current value type (e.g. newline keywords).
982 if constexpr(requires(ossia::value v) { F::migrate_value(v); })
983 {
984 auto inlet = qobject_cast<Process::ControlInlet*>(
985 avnd_input_idx_to_model_ports(Idx)[0]);
986 inlet->setValue(F::migrate_value(inlet->value()));
987 }
988 }
989 if constexpr(requires { F::on_controller_setup(); })
990 {
991 auto controller_inlets = avnd_input_idx_to_model_ports(Idx);
992 SCORE_ASSERT(controller_inlets.size() == 1);
993 auto inlet = qobject_cast<Process::ControlInlet*>(controller_inlets[0]);
994
995 oscr::from_ossia_value(inlet->value(), field.value);
996
997 if_possible(field.update(obj));
998
999 F::on_controller_setup()(obj, field.value);
1000 }
1001 if constexpr(requires { F::on_controller_interaction(); })
1002 {
1003 auto controller_inlets = avnd_input_idx_to_model_ports(Idx);
1004 SCORE_ASSERT(controller_inlets.size() == 1);
1005 auto inlet = qobject_cast<Process::ControlInlet*>(controller_inlets[0]);
1006 inlet->noValueChangeOnMove = true;
1007 inlet->changesPorts = true;
1008
1009 if constexpr(!requires { F::on_controller_setup(); })
1010 {
1011 oscr::from_ossia_value(inlet->value(), field.value);
1012 if_possible(field.update(obj));
1013 F::on_controller_interaction()(obj, field.value);
1014 }
1015
1016 connect(
1017 inlet, &Process::ControlInlet::valueChanged,
1018 [this, &field](const ossia::value& val) {
1019 Info& obj = this->object_storage_for_ports_callbacks;
1020 oscr::from_ossia_value(val, field.value);
1021
1022 if_possible(field.update(obj));
1023
1024 F::on_controller_interaction()(obj, field.value);
1025 });
1026 }
1027 });
1028
1029 if constexpr(avnd::has_gui_to_processor_bus<Info>)
1030 {
1031 // FIXME needs to be a list of callbacks?
1032 }
1033
1034 if constexpr(avnd::has_processor_to_gui_bus<Info>)
1035 {
1036 Info& obj = this->object_storage_for_ports_callbacks;
1037
1038 obj.send_message = [this]<typename T>(T&& b) mutable {
1039 if(this->to_ui)
1040 MessageBusSender{this->to_ui}(std::move(b));
1041 };
1042 }
1043 }
1044 }
1045
1046 void init_dynamic_ports()
1047 {
1048 // To check:
1049
1050 // - serialization
1051 // - OK: uses of total_input_count / total_output_count
1052
1053 // - OK: uses of oscr::modelPort (btw this does not seem to match total_input_count)
1054
1055 // - execution, resize the avnd object with the number of ports
1056 // -> connect controls so that they change in the ui
1057
1058 // - OK: Layer::createControl: uses index_in_struct and assumes it's a ProcessModel::inlet index
1059
1060 // How are things going to happen UI-wise: all controls created one after each other ?
1061 // what if we want to create separate tabs with e.g.
1062 // control A 1, control B 1
1063 // control A 2, control B 2
1064 // we need to know the current state of dynamic ports
1065
1066 // UI: recreating UI causes crash when editing e.g. context menu of spinbox
1067 // UI: recreating UI causes bug when editing e.g. spinbox
1068
1069 // -> we have to find a way to detect that the currently edited object is being deleted
1070
1071 // other option: mark widget as "unsafe", do not have it send value changes until mouse is released
1072
1073 // REloading on crash: does crash, because the control change does not have
1074 // the time to be applied. In DocumentBuilder::loadCommandStack we have
1075 // to process the event loop in-between events.
1076 // FIxed if we put zero but then changing the widget with the mouses crashes because of the comment
1077 // below...
1078 // ->
1079 // terminate called after throwing an instance of 'std::runtime_error'
1080 // what(): Ongoing command mismatch: current command SetControlValue does not match new command MoveNodes
1081
1082 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1083 {
1084 Info& obj = object_storage_for_ports_callbacks;
1085 avnd::dynamic_ports_input_introspection<Info>::for_all_n2(
1086 avnd::get_inputs(obj),
1087 [&]<std::size_t N>(auto& port, auto pred_idx, avnd::field_index<N> field_idx) {
1088 port.request_port_resize = [this, &port](int new_count) {
1089 this->request_new_dynamic_input_count(port, avnd::field_index<N>{}, new_count);
1090 };
1091 if constexpr(requires { port.request_port_rows; })
1092 port.request_port_rows = [this, &port](const auto& rows) {
1093 this->template request_dynamic_rows<true>(
1094 port, avnd::field_index<N>{}, rows);
1095 };
1096 });
1097 }
1098
1099 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1100 {
1101 Info& obj = object_storage_for_ports_callbacks;
1102 avnd::dynamic_ports_output_introspection<Info>::for_all_n2(
1103 avnd::get_outputs(obj),
1104 [&]<std::size_t N>(auto& port, auto pred_idx, avnd::field_index<N> field_idx) {
1105 port.request_port_resize = [this, &port](int new_count) {
1106 this->request_new_dynamic_output_count(
1107 port, avnd::field_index<N>{}, new_count);
1108 };
1109 if constexpr(requires { port.request_port_rows; })
1110 port.request_port_rows = [this, &port](const auto& rows) {
1111 this->template request_dynamic_rows<false>(
1112 port, avnd::field_index<N>{}, rows);
1113 };
1114 });
1115 }
1116 }
1117
1118 template <bool Input, typename P, std::size_t N, typename Rows>
1119 void request_dynamic_rows(P& port, avnd::field_index<N> idx, const Rows& rows)
1120 {
1121 if(rows.size() > 512)
1122 return;
1123 std::vector<int> keys;
1124 keys.reserve(rows.size());
1125 for(const auto& [key, text] : rows)
1126 {
1127 if(key < 10000 || ossia::contains(keys, key))
1128 return;
1129 keys.push_back(key);
1130 }
1131 auto& all = [&]() -> auto& {
1132 if constexpr(Input)
1133 return m_inlets;
1134 else
1135 return m_outlets;
1136 }();
1137 auto first = [&] {
1138 if constexpr(Input)
1139 return avnd_input_idx_to_iterator(idx);
1140 else
1141 return avnd_output_idx_to_iterator(idx);
1142 }();
1143 auto& count = [&]() -> auto& {
1144 if constexpr(Input)
1145 return dynamic_ports.num_in_ports(idx);
1146 else
1147 return dynamic_ports.num_out_ports(idx);
1148 }();
1149 using Ports = std::decay_t<decltype(all)>;
1150 using Port = std::remove_pointer_t<typename Ports::value_type>;
1151 Ports old{first, first + count}, next;
1152 next.reserve(rows.size());
1153 // A row without text is named like a port of a count-based group: the
1154 // port's name with "{}" replaced by its position ("Input 2").
1155 using port_type = std::decay_t<decltype(port.ports[0])>;
1156 const auto templateName = [] {
1157 const std::string_view nm = avnd::get_name<port_type>();
1158 return QString::fromUtf8(nm.data(), nm.size());
1159 }();
1160 int row = 0;
1161 for(const auto& [key, text] : rows)
1162 {
1163 auto found
1164 = ossia::find_if(old, [key](auto p) { return p && p->id().val() == key; });
1165 Port* p{};
1166 if(found != old.end())
1167 {
1168 p = *found;
1169 *found = nullptr;
1170 }
1171 else
1172 {
1173 Ports added;
1174 if constexpr(Input)
1175 {
1176 InletInitFunc<Info> make{*this, added};
1177 make.inlet = key;
1178 make(port, idx);
1179 }
1180 else
1181 {
1182 OutletInitFunc<Info> make{*this, added};
1183 make.outlet = key;
1184 make(port, idx);
1185 }
1186 SCORE_ASSERT(added.size() == 1);
1187 p = added.front();
1188 }
1189 p->stableIdentity = true;
1190 auto name = QString::fromStdString(text);
1191 if(name.trimmed().isEmpty())
1192 {
1193 name = templateName;
1194 name.replace(QStringLiteral("{}"), QString::number(row));
1195 }
1196 p->setName(name);
1197 next.push_back(p);
1198 row++;
1199 }
1200 const bool changed = !std::equal(next.begin(), next.end(), first, first + count);
1201 const auto offset = first - all.begin();
1202 all.erase(first, first + count);
1203 all.insert(all.begin() + offset, next.begin(), next.end());
1204 count = rows.size();
1205 ossia::small_pod_vector<Port*, 4> removed;
1206 for(auto p : old)
1207 if(p)
1208 removed.push_back(p);
1209 removeCablesOfPorts(removed);
1210 if(changed)
1211 {
1212 if constexpr(Input)
1213 inletsChanged();
1214 else
1215 outletsChanged();
1216 }
1217 for(auto p : removed)
1218 delete p;
1219 }
1220
1225 template <typename Port_T>
1226 void removeCablesOfPorts(const ossia::small_pod_vector<Port_T*, 4>& ports)
1227 {
1228 if(ports.empty())
1229 return;
1230
1231 // Not score::IDocument::documentFromObject: it throws when there is no
1232 // document, and a process can be resized outside of one.
1233 score::Document* doc{};
1234 for(QObject* o = this->parent(); o && !doc; o = o->parent())
1235 doc = qobject_cast<score::Document*>(o);
1236 if(!doc)
1237 return;
1238
1239 QObjectList objs;
1240 objs.reserve(ports.size());
1241 for(auto p : ports)
1242 objs.push_back(p);
1243
1244 const auto& ctx = doc->context();
1245 if(auto cables = Dataflow::saveCables(objs, ctx); !cables.empty())
1246 Dataflow::removeCables(cables, ctx);
1247 }
1248
1249 template <typename P, std::size_t N>
1250 void request_new_dynamic_input_count(P& port, avnd::field_index<N> idx, int count)
1251 {
1252 const int current_model_ports = dynamic_ports.num_in_ports(idx);
1253 if(current_model_ports == count || count < 0 || count > 512)
1254 return;
1255
1256 ossia::small_pod_vector<Process::Inlet*, 4> to_delete;
1257 if(current_model_ports < count)
1258 {
1259 // Add new ports
1260 // 1. Find the location where to add them
1261 auto res = avnd_input_idx_to_iterator(idx);
1262 res += current_model_ports;
1263 Process::Inlets inlets_to_add;
1264 InletInitFunc<Info> inlets{*this, inlets_to_add};
1265 inlets.inlet = 10000 + N * 1000 + current_model_ports;
1266 int sz = 0;
1267 for(int i = current_model_ports; i < count; i++)
1268 {
1269 inlets(port, idx);
1270 if(std::ssize(inlets_to_add) > sz)
1271 {
1272 sz = std::ssize(inlets_to_add);
1273 auto new_inlet = inlets_to_add.back();
1274 if(auto nm = new_inlet->name(); nm.contains("{}"))
1275 {
1276 nm.replace("{}", QString::number(i));
1277 new_inlet->setName(nm);
1278 }
1279 }
1280 }
1281 m_inlets.insert(res, inlets_to_add.begin(), inlets_to_add.end());
1282 }
1283 else if(current_model_ports > count)
1284 {
1285 // Delete the ports
1286 auto res = avnd_input_idx_to_iterator(idx);
1287 res += count;
1288 auto begin_deleted = res;
1289 for(int i = 0; i < (current_model_ports - count); i++)
1290 {
1291 to_delete.push_back(*res);
1292 ++res;
1293 }
1294 m_inlets.erase(begin_deleted, res);
1295 }
1296
1297 dynamic_ports.num_in_ports(idx) = count;
1298
1299 removeCablesOfPorts(to_delete);
1300 inletsChanged();
1301 for(auto port : to_delete)
1302 delete port;
1303 }
1304
1305 template <typename P, std::size_t N>
1306 void request_new_dynamic_output_count(P& port, avnd::field_index<N> idx, int count)
1307 {
1308 const int current_model_ports = dynamic_ports.num_out_ports(idx);
1309 if(current_model_ports == count || count < 0 || count > 512)
1310 return;
1311
1312 ossia::small_pod_vector<Process::Outlet*, 4> to_delete;
1313 if(current_model_ports < count)
1314 {
1315 // Add new ports
1316 // 1. Find the location where to add them
1317 auto res = avnd_output_idx_to_iterator(idx);
1318 res += current_model_ports;
1319 Process::Outlets outlets_to_add;
1320 OutletInitFunc<Info> outlets{*this, outlets_to_add};
1321 outlets.outlet = 1000000 + N * 1000 + current_model_ports;
1322 int sz = 0;
1323 for(int i = current_model_ports; i < count; i++)
1324 {
1325 outlets(port, idx);
1326 if(std::ssize(outlets_to_add) > sz)
1327 {
1328 sz = std::ssize(outlets_to_add);
1329 auto new_outlet = outlets_to_add.back();
1330 if(auto nm = new_outlet->name(); nm.contains("{}"))
1331 {
1332 nm.replace("{}", QString::number(i));
1333 new_outlet->setName(nm);
1334 }
1335 }
1336 }
1337 m_outlets.insert(res, outlets_to_add.begin(), outlets_to_add.end());
1338 }
1339 else if(current_model_ports > count)
1340 {
1341 // Delete the ports
1342 auto res = avnd_output_idx_to_iterator(idx);
1343 res += count;
1344 auto begin_deleted = res;
1345 for(int i = 0; i < (current_model_ports - count); i++)
1346 {
1347 to_delete.push_back(*res);
1348 ++res;
1349 }
1350 m_outlets.erase(begin_deleted, res);
1351 }
1352
1353 dynamic_ports.num_out_ports(idx) = count;
1354
1355 removeCablesOfPorts(to_delete);
1356 outletsChanged();
1357 for(auto port : to_delete)
1358 delete port;
1359 }
1360
1364 void init_dynamic_port_instances()
1365 {
1366 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1367 {
1368 avnd::dynamic_ports_input_introspection<Info>::for_all_n2(
1369 avnd::get_inputs((Info&)object_storage_for_ports_callbacks),
1370 [&]<std::size_t N>(auto& port, auto, avnd::field_index<N> idx) {
1371 auto& count = dynamic_ports.num_in_ports(idx);
1372 const int n = std::exchange(count, 0);
1373 request_new_dynamic_input_count(port, idx, n);
1374 });
1375 }
1376 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1377 {
1378 avnd::dynamic_ports_output_introspection<Info>::for_all_n2(
1379 avnd::get_outputs((Info&)object_storage_for_ports_callbacks),
1380 [&]<std::size_t N>(auto& port, auto, avnd::field_index<N> idx) {
1381 auto& count = dynamic_ports.num_out_ports(idx);
1382 const int n = std::exchange(count, 0);
1383 request_new_dynamic_output_count(port, idx, n);
1384 });
1385 }
1386 }
1387
1388 void init_all_ports()
1389 {
1390 InletInitFunc<Info> inlets{*this, m_inlets};
1391 OutletInitFunc<Info> outlets{*this, m_outlets};
1392 avnd::port_visit_dispatcher<Info>([&inlets]<typename P>(P&& port, auto idx) {
1393 if constexpr(!avnd::dynamic_ports_port<P>)
1394 inlets(port, idx);
1395 }, [&outlets]<typename P>(P&& port, auto idx) {
1396 if constexpr(!avnd::dynamic_ports_port<P>)
1397 outlets(port, idx);
1398 });
1399
1400 if(!requires { Info::ossia_show_ports_by_default; })
1401 if constexpr(oscr::has_ossia_layer<Info>)
1402 hideAllInlets(*this, !avnd::tag_fully_custom_item<Info>);
1403 }
1404
1405public:
1406 int expected_input_ports() const noexcept
1407 {
1408 int count = 0;
1409
1410 // We have to adjust before accessing a port as there is the first "fake"
1411 // port if the processor takes audio by argument
1412 if constexpr(avnd::audio_argument_processor<Info>)
1413 count += 1;
1414 else if constexpr(avnd::tag_cv<Info>)
1415 count += 1;
1416
1417 // The "messages" ports also go before
1418 count += avnd::messages_introspection<Info>::size;
1419
1420 avnd::input_introspection<Info>::for_all([this, &count]<std::size_t Idx, typename P>(
1421 avnd::field_reflection<Idx, P> field) {
1422 int num_ports = 1;
1423 if constexpr(avnd::dynamic_ports_port<P>)
1424 num_ports = dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1425 count += num_ports;
1426 });
1427
1428 return count;
1429 }
1430
1431 int expected_output_ports() const noexcept
1432 {
1433 int count = 0;
1434
1435 // We have to adjust before accessing a port as there is the first "fake"
1436 // port if the processor takes audio by argument
1437 if constexpr(avnd::audio_argument_processor<Info>)
1438 count += 1;
1439 else if constexpr(avnd::tag_cv<Info>)
1440 {
1441 using operator_ret = typename avnd::function_reflection_o<Info>::return_type;
1442 if constexpr(!std::is_void_v<operator_ret>)
1443 count += 1;
1444 }
1445
1446 avnd::output_introspection<Info>::for_all(
1447 [this,
1448 &count]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1449 int num_ports = 1;
1450 if constexpr(avnd::dynamic_ports_port<P>)
1451 num_ports = dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1452 count += num_ports;
1453 });
1454
1455 return count;
1456 }
1457
1458 std::span<Process::Inlet*> avnd_input_idx_to_model_ports(int index) const noexcept
1459 {
1460 int model_index = 0;
1461
1462 // We have to adjust before accessing a port as there is the first "fake"
1463 // port if the processor takes audio by argument
1464 if constexpr(avnd::audio_argument_processor<Info>)
1465 model_index += 1;
1466 else if constexpr(avnd::tag_cv<Info>)
1467 model_index += 1;
1468
1469 // The "messages" ports also go before
1470 model_index += avnd::messages_introspection<Info>::size;
1471
1472 std::span<Process::Inlet*> ret;
1473 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size == 0)
1474 {
1475 ret = std::span<Process::Inlet*>(
1476 const_cast<Process::Inlet**>(this->m_inlets.data()) + model_index + index, 1);
1477 }
1478 else
1479 {
1480 avnd::input_introspection<Info>::for_all(
1481 [this, index, &model_index,
1482 &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1483 if(Idx == index)
1484 {
1485 int num_ports = 1;
1486 if constexpr(avnd::dynamic_ports_port<P>)
1487 {
1488 num_ports = dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1489 if(num_ports == 0)
1490 {
1491 ret = {};
1492 return;
1493 }
1494 }
1495 ret = std::span<Process::Inlet*>(
1496 const_cast<Process::Inlet**>(this->m_inlets.data()) + model_index,
1497 num_ports);
1498 }
1499 else
1500 {
1501 if constexpr(avnd::dynamic_ports_port<P>)
1502 {
1503 model_index += dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1504 }
1505 else
1506 {
1507 model_index += 1;
1508 }
1509 }
1510 });
1511 }
1512
1513 return ret;
1514 }
1515
1516 std::span<Process::Outlet*> avnd_output_idx_to_model_ports(int index) const noexcept
1517 {
1518 int model_index = 0;
1519
1520 // We have to adjust before accessing a port as there is the first "fake"
1521 // port if the processor takes audio by argument
1522 if constexpr(avnd::audio_argument_processor<Info>)
1523 model_index += 1;
1524 else if constexpr(avnd::tag_cv<Info>)
1525 {
1526 using operator_ret = typename avnd::function_reflection_o<Info>::return_type;
1527 if constexpr(!std::is_void_v<operator_ret>)
1528 model_index += 1;
1529 }
1530
1531 // The "messages" ports also go before
1532 model_index += avnd::messages_introspection<Info>::size;
1533
1534 std::span<Process::Outlet*> ret;
1535 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size == 0)
1536 {
1537 ret = std::span<Process::Outlet*>(
1538 const_cast<Process::Outlet**>(this->m_outlets.data()) + model_index + index,
1539 1);
1540 }
1541 else
1542 {
1543 avnd::output_introspection<Info>::for_all(
1544 [this, index, &model_index,
1545 &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1546 if(Idx == index)
1547 {
1548 int num_ports = 1;
1549 if constexpr(avnd::dynamic_ports_port<P>)
1550 {
1551 num_ports = dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1552 if(num_ports == 0)
1553 {
1554 ret = {};
1555 return;
1556 }
1557 }
1558 ret = std::span<Process::Outlet*>(
1559 const_cast<Process::Outlet**>(this->m_outlets.data()) + model_index,
1560 num_ports);
1561 }
1562 else
1563 {
1564 if constexpr(avnd::dynamic_ports_port<P>)
1565 {
1566 model_index += dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1567 }
1568 else
1569 {
1570 model_index += 1;
1571 }
1572 }
1573 });
1574 }
1575
1576 return ret;
1577 }
1578
1579 Process::Inlets::iterator avnd_input_idx_to_iterator(int index) const noexcept
1580 {
1581 int model_index = 0;
1582
1583 // We have to adjust before accessing a port as there is the first "fake"
1584 // port if the processor takes audio by argument
1585 // (same adjustment as avnd_input_idx_to_model_ports)
1586 if constexpr(avnd::audio_argument_processor<Info>)
1587 model_index += 1;
1588 else if constexpr(avnd::tag_cv<Info>)
1589 model_index += 1;
1590
1591 // The "messages" ports also go before
1592 model_index += avnd::messages_introspection<Info>::size;
1593
1594 Process::Inlets::iterator ret;
1595 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size == 0)
1596 {
1597 ret = const_cast<ProcessModel*>(this)->m_inlets.begin() + model_index;
1598 }
1599 else
1600 {
1601 avnd::input_introspection<Info>::for_all(
1602 [this, index, &model_index,
1603 &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1604 if(Idx == index)
1605 {
1606 ret = const_cast<ProcessModel*>(this)->m_inlets.begin() + model_index;
1607 }
1608 else
1609 {
1610 if constexpr(avnd::dynamic_ports_port<P>)
1611 {
1612 model_index += dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1613 }
1614 else
1615 {
1616 model_index += 1;
1617 }
1618 }
1619 });
1620 }
1621 return ret;
1622 }
1623
1624 Process::Outlets::iterator avnd_output_idx_to_iterator(int index) const noexcept
1625 {
1626 int model_index = 0;
1627
1628 // We have to adjust before accessing a port as there is the first "fake"
1629 // port if the processor takes audio by argument
1630 // (same adjustment as avnd_output_idx_to_model_ports: no message outlets,
1631 // and a cv processor only has an output if it returns something)
1632 if constexpr(avnd::audio_argument_processor<Info>)
1633 model_index += 1;
1634 else if constexpr(avnd::tag_cv<Info>)
1635 {
1636 using operator_ret = typename avnd::function_reflection_o<Info>::return_type;
1637 if constexpr(!std::is_void_v<operator_ret>)
1638 model_index += 1;
1639 }
1640
1641 Process::Outlets::iterator ret;
1642 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size == 0)
1643 {
1644 ret = const_cast<ProcessModel*>(this)->m_outlets.begin() + model_index;
1645 }
1646 else
1647 {
1648 avnd::output_introspection<Info>::for_all(
1649 [this, index, &model_index,
1650 &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1651 if(Idx == index)
1652 {
1653 ret = const_cast<ProcessModel*>(this)->m_outlets.begin() + model_index;
1654 }
1655 else
1656 {
1657 if constexpr(avnd::dynamic_ports_port<P>)
1658 {
1659 model_index += dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1660 }
1661 else
1662 {
1663 model_index += 1;
1664 }
1665 }
1666 });
1667 }
1668 return ret;
1669 }
1670};
1671}
1672
1673template <typename Info>
1674struct is_custom_serialized<oscr::ProcessModel<Info>> : std::true_type
1675{
1676};
1677
1678template <typename Info>
1679struct TSerializer<DataStream, oscr::ProcessModel<Info>>
1680{
1682 static void readFrom(DataStream::Serializer& s, const model_type& obj)
1683 {
1684 Process::readPorts(s, obj.m_inlets, obj.m_outlets);
1685
1686 // Save the recorded amount of dynamic ports for each port
1687 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1688 {
1689 avnd::dynamic_ports_input_introspection<Info>::for_all(
1690 [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1691 if constexpr(avnd::dynamic_ports_port<P>)
1692 s.stream() << obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1693 });
1694 }
1695 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1696 {
1697 avnd::dynamic_ports_output_introspection<Info>::for_all(
1698 [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1699 if constexpr(avnd::dynamic_ports_port<P>)
1700 s.stream() << obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1701 });
1702 }
1703
1704 s.insertDelimiter();
1705 }
1706
1707 static void writeTo(DataStream::Deserializer& s, model_type& obj)
1708 {
1709 Process::writePorts(
1710 s, s.components.interfaces<Process::PortFactoryList>(), obj.m_inlets,
1711 obj.m_outlets, &obj);
1712
1713 // Read the recorded amount of dynamic ports for each port
1714 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1715 {
1716 avnd::dynamic_ports_input_introspection<Info>::for_all(
1717 [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1718 if constexpr(avnd::dynamic_ports_port<P>)
1719 s.stream() >> obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1720 });
1721 }
1722 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1723 {
1724 avnd::dynamic_ports_output_introspection<Info>::for_all(
1725 [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1726 if constexpr(avnd::dynamic_ports_port<P>)
1727 s.stream() >> obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1728 });
1729 }
1730 s.checkDelimiter();
1731 }
1732};
1733
1734template <typename Info>
1735struct TSerializer<JSONObject, oscr::ProcessModel<Info>>
1736{
1738 static void readFrom(JSONObject::Serializer& s, const model_type& obj)
1739 {
1740 Process::readPorts(s, obj.m_inlets, obj.m_outlets);
1741 // Save the recorded amount of dynamic ports for each port
1742 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1743 {
1744 ossia::string_map<int> indices;
1745 avnd::dynamic_ports_input_introspection<Info>::for_all(
1746 [&obj,
1747 &indices]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1748 if constexpr(avnd::dynamic_ports_port<P>)
1749 {
1750 indices[std::string(avnd::get_c_identifier<P>())]
1751 = obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
1752 }
1753 });
1754 s.obj["DynamicInlets"] = indices;
1755 }
1756 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1757 {
1758 ossia::string_map<int> indices;
1759 avnd::dynamic_ports_output_introspection<Info>::for_all(
1760 [&obj,
1761 &indices]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1762 if constexpr(avnd::dynamic_ports_port<P>)
1763 indices[std::string(avnd::get_c_identifier<P>())]
1764 = obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
1765 });
1766 s.obj["DynamicOutlets"] = indices;
1767 }
1768 }
1769
1770 static void writeTo(JSONObject::Deserializer& s, model_type& obj)
1771 {
1772 Process::writePorts(
1773 s, s.components.interfaces<Process::PortFactoryList>(), obj.m_inlets,
1774 obj.m_outlets, &obj);
1775 if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
1776 {
1777 if(auto val = s.obj.tryGet("DynamicInlets"))
1778 {
1779 static ossia::string_map<int> indices;
1780 indices.clear();
1781 indices <<= *val;
1782 avnd::dynamic_ports_input_introspection<Info>::for_all(
1783 [&obj]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1784 if constexpr(avnd::dynamic_ports_port<P>)
1785 obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{})
1786 = indices[std::string(avnd::get_c_identifier<P>())];
1787 });
1788 }
1789 }
1790 if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
1791 {
1792 if(auto val = s.obj.tryGet("DynamicOutlets"))
1793 {
1794 static ossia::string_map<int> indices;
1795 indices.clear();
1796 indices <<= *val;
1797 avnd::dynamic_ports_output_introspection<Info>::for_all(
1798 [&obj]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
1799 if constexpr(avnd::dynamic_ports_port<P>)
1800 obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{})
1801 = indices[std::string(avnd::get_c_identifier<P>())];
1802 });
1803 }
1804 }
1805 }
1806};
Definition VisitorInterface.hpp:53
Definition DataStreamVisitor.hpp:27
Definition DataStreamVisitor.hpp:202
Definition VisitorInterface.hpp:61
Definition JSONVisitor.hpp:52
void readFrom(const score::Entity< T > &obj)
Definition EntitySerialization.hpp:7
Definition JSONVisitor.hpp:423
Definition Port.hpp:247
Definition Port.hpp:221
Definition Port.hpp:329
Definition PortFactory.hpp:121
Definition Port.hpp:106
bool changesPorts
The value of this control decides which ports the process has.
Definition Port.hpp:118
The Process class.
Definition score-lib-process/Process/Process.hpp:82
QPointF position() const noexcept
Nodal things.
Definition score-lib-process/Process/Process.cpp:338
Definition UuidKey.hpp:345
The id_base_t class.
Definition Identifier.hpp:59
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:123
Process::Preset savePreset() const noexcept override
[id, value, name] for each control: see loadPreset.
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:249
void loadPreset(const Process::Preset &preset) override
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:224
The Document class is the central part of the software.
Definition Document.hpp:51
Definition Metadatas.hpp:93
Definition Concepts.hpp:50
Base classes and tools to implement processes and layers.
Definition JSONVisitor.hpp:1115
@ Input
Read by the process: must be collected next to the document.
Definition Controls.hpp:30
void hideAllInlets(Process::ProcessModel &proc, bool outlets)
A fully custom item shows the inlets it wants; the node shows its outlets.
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:109
QString locateFilePath(const QString &filename) noexcept
Definition File.cpp:84
STL namespace.
Definition CableHelpers.hpp:102
Definition JSONVisitor.hpp:372
Static metadata implementation.
Definition lib/score/tools/Metadata.hpp:36
Definition PortForward.hpp:23
Definition PortForward.hpp:27
Definition Preset.hpp:32
Definition VisitorInterface.hpp:13
Definition TimeValue.hpp:21
Definition ObjectMatches.hpp:6
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:54
std::vector< std::pair< QPointer< QObject >, std::function< void(QByteArray)> > > to_ui_listeners
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:72
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:49
Definition MIDISync.hpp:149