score-plugin-avnd/Crousti/ProcessModel.hpp
1 #pragma once
2 #include <Process/Dataflow/Port.hpp>
3 #include <Process/Dataflow/PortFactory.hpp>
4 #include <Process/GenericProcessFactory.hpp>
5 #include <Process/Process.hpp>
6 #include <Process/ProcessFactory.hpp>
7 #include <Process/ProcessMetadata.hpp>
8 
9 #include <Crousti/Attributes.hpp>
10 #include <Crousti/Concepts.hpp>
11 #include <Crousti/Metadatas.hpp>
12 #include <Media/Sound/Drop/SoundDrop.hpp>
13 
14 #include <ossia/detail/typelist.hpp>
15 
16 #include <boost/pfr.hpp>
17 
18 #include <avnd/common/for_nth.hpp>
19 #include <avnd/concepts/file_port.hpp>
20 #include <avnd/concepts/gfx.hpp>
21 #include <avnd/concepts/temporality.hpp>
22 #include <avnd/concepts/ui.hpp>
23 #include <avnd/introspection/messages.hpp>
24 #include <avnd/wrappers/bus_host_process_adapter.hpp>
25 #include <avnd/wrappers/metadatas.hpp>
26 
27 #include <score_plugin_engine.hpp>
28 
29 #if SCORE_PLUGIN_GFX
30 #include <Gfx/TexturePort.hpp>
31 #endif
36 inline QString fromStringView(std::string_view v)
37 {
38  return QString::fromUtf8(v.data(), v.size());
39 }
41 namespace oscr
42 {
43 template <typename Info>
44 class ProcessModel;
45 }
46 template <typename Info>
47  requires avnd::has_name<Info>
48 struct Metadata<PrettyName_k, oscr::ProcessModel<Info>>
49 {
50  static constexpr const char* get() noexcept { return avnd::get_name<Info>().data(); }
51 };
52 template <typename Info>
53  requires avnd::has_category<Info>
54 struct Metadata<Category_k, oscr::ProcessModel<Info>>
55 {
56  static constexpr const char* get() noexcept
57  {
58  return avnd::get_category<Info>().data();
59  }
60 };
61 template <typename Info>
62  requires(!avnd::has_category<Info>)
63 struct Metadata<Category_k, oscr::ProcessModel<Info>>
64 {
65  static constexpr const char* get() noexcept { return ""; }
66 };
67 
68 template <typename Info>
69 struct Metadata<Tags_k, oscr::ProcessModel<Info>>
70 {
71  static QStringList get() noexcept
72  {
73  QStringList lst;
74  for(std::string_view tag : avnd::get_tags<Info>())
75  lst.push_back(QString::fromUtf8(tag.data(), tag.size()));
76  return lst;
77  }
78 };
79 
80 template <typename T>
81 concept has_kind = requires { T::kind(); };
82 
83 template <typename T>
84 auto get_kind()
85 {
86  if constexpr(has_kind<T>)
87  return T::kind();
88  else
89  return Process::ProcessCategory::Other;
90 }
91 
92 template <typename Info>
93 struct Metadata<Process::Descriptor_k, oscr::ProcessModel<Info>>
94 {
95  static std::vector<Process::PortType> inletDescription()
96  {
97  std::vector<Process::PortType> port;
98  /*
99  for (std::size_t i = 0; i < info::audio_in_count; i++)
100  port.push_back(Process::PortType::Audio);
101  for (std::size_t i = 0; i < info::midi_in_count; i++)
102  port.push_back(Process::PortType::Midi);
103  for (std::size_t i = 0; i < info::value_in_count; i++)
104  port.push_back(Process::PortType::Message);
105  for (std::size_t i = 0; i < info::control_in_count; i++)
106  port.push_back(Process::PortType::Message);
107  */
108  return port;
109  }
110  static std::vector<Process::PortType> outletDescription()
111  {
112  std::vector<Process::PortType> port;
113  /*
114  for (std::size_t i = 0; i < info::audio_out_count; i++)
115  port.push_back(Process::PortType::Audio);
116  for (std::size_t i = 0; i < info::midi_out_count; i++)
117  port.push_back(Process::PortType::Midi);
118  for (std::size_t i = 0; i < info::value_out_count; i++)
119  port.push_back(Process::PortType::Message);
120  for (std::size_t i = 0; i < info::control_out_count; i++)
121  port.push_back(Process::PortType::Message);
122  */
123  return port;
124  }
125  static Process::Descriptor get()
126  {
127 // literate programming goes brr
128 #if defined(_MSC_VER)
129 #define if_exists(Expr, Else) []() noexcept { if(false) {} Else; } ()
130 #define if_attribute(Attr) QString{}
131 #else
132 #define if_exists(Expr, Else) \
133  []() noexcept { \
134  if constexpr(requires { Expr; }) \
135  return Expr; \
136  Else; \
137  }()
138 
139 #define if_attribute(Attr) \
140  []() noexcept -> QString { \
141  if constexpr(avnd::has_##Attr<Info>) \
142  return fromStringView(avnd::get_##Attr<Info>()); \
143  else \
144  return QString{}; \
145  }()
146 #endif
147  static Process::Descriptor desc
148  {
150  if_exists(Info::kind(), else return Process::ProcessCategory::Other;),
151  if_attribute(category), if_attribute(description), if_attribute(author),
152  Metadata<Tags_k, oscr::ProcessModel<Info>>::get(), inletDescription(),
153  outletDescription()
154  };
155  return desc;
156  }
157 };
158 template <typename Info>
160 {
161  static Process::ProcessFlags get() noexcept
162  {
163  if constexpr(requires { Info::flags(); })
164  {
165  return Info::flags();
166  }
167  else
168  {
170  Process::ProcessFlags::SupportsLasting
171  | Process::ProcessFlags::ControlSurface);
172 
173  if constexpr(avnd::tag_single_exec<Info>)
174  flags |= Process::ProcessFlags::SupportsState;
175 
176  return flags;
177  }
178  }
179 };
180 template <typename Info>
181 struct Metadata<ObjectKey_k, oscr::ProcessModel<Info>>
182 {
183  static constexpr auto get() noexcept { return avnd::get_c_name<Info>().data(); }
184 };
185 template <typename Info>
186 struct Metadata<ConcreteKey_k, oscr::ProcessModel<Info>>
187 {
188  static Q_DECL_RELAXED_CONSTEXPR UuidKey<Process::ProcessModel> get()
189  {
190  return oscr::uuid_from_string<Info>();
191  }
192 };
193 
194 namespace oscr
195 {
196 
197 template <typename T>
198 auto& modelPort(auto& ports, int index)
199 {
200  // We have to adjust before accessing a port as there is the first "fake"
201  // port if the processor takes audio by argument
202  if constexpr(avnd::audio_argument_processor<T>)
203  index += 1;
204 
205  // The "messages" ports also go before
206  index += avnd::messages_introspection<T>::size;
207 
208  return ports[index];
209 }
210 
211 template <typename T>
212 inline void setupNewPort(Process::Port* obj)
213 {
214  //FIXME
215 #if !defined(__APPLE__)
216  constexpr auto name = avnd::get_name<T>();
217  obj->setName(fromStringView(name));
218 
219  if constexpr(constexpr auto desc = avnd::get_description<T>(); !desc.empty())
220  obj->setDescription(fromStringView(desc));
221 #else
222  auto name = avnd::get_name<T>();
223  obj->setName(fromStringView(name));
224  if(auto desc = avnd::get_description<T>(); !desc.empty())
225  obj->setDescription(fromStringView(desc));
226 #endif
227 }
228 
229 template <std::size_t N, typename T>
230 inline void setupNewPort(const avnd::field_reflection<N, T>& spec, Process::Port* obj)
231 {
232  setupNewPort<T>(obj);
233 }
234 
235 template <typename T>
236 inline void setupNewPort(const T& spec, Process::Port* obj)
237 {
238  setupNewPort<T>(obj);
239 }
240 
241 template <typename Node>
243 {
244  Process::ProcessModel& self;
245  Process::Inlets& ins;
246  int inlet = 0;
247 
248  void operator()(const avnd::audio_port auto& in, auto idx)
249  {
250  auto p = new Process::AudioInlet(Id<Process::Port>(inlet++), &self);
251  setupNewPort(in, p);
252  ins.push_back(p);
253  }
254  void operator()(const avnd::midi_port auto& in, auto idx)
255  {
256  auto p = new Process::MidiInlet(Id<Process::Port>(inlet++), &self);
257  setupNewPort(in, p);
258  ins.push_back(p);
259  }
260 
261  static QString toFilters(const QSet<QString>& exts)
262  {
263  QString res;
264  for(const auto& s : exts)
265  {
266  res += "*.";
267  res += s;
268  res += " ";
269  }
270  if(!res.isEmpty())
271  res.resize(res.size() - 1);
272  return res;
273  }
274 
275  template <typename P>
276  static QString getFilters(P& p)
277  {
278  if constexpr(requires { P::filters(); })
279  {
280  using filter = decltype(P::filters());
281  if constexpr(
282  requires { filter::sound; } || requires { filter::audio; })
283  {
284  return QString{"Sound files (%1)"}.arg(
285  toFilters(Media::Sound::DropHandler{}.fileExtensions()));
286  }
287  else if constexpr(requires { filter::video; })
288  {
289  // FIXME refactor supported formats with Video process
290  QSet<QString> files = {"mkv", "mov", "mp4", "h264", "avi", "hap", "mpg",
291  "mpeg", "imf", "mxf", "mts", "m2ts", "mj2", "webm"};
292  return QString{"Videos (%1)"}.arg(toFilters(files));
293  }
294  else if constexpr(requires { filter::image; })
295  {
296  // FIXME refactor supported formats with Image List Chooser
297  return QString{"Images (*.png *.jpg *.jpeg *.gif *.bmp *.tiff)"};
298  }
299  else if constexpr(requires { filter::midi; })
300  {
301  return "MIDI (*.mid)";
302  }
303  else if constexpr(avnd::string_ish<filter>)
304  {
305  constexpr auto text_filters = P::filters();
306  return fromStringView(P::filters());
307  }
308  else
309  {
310  return "";
311  }
312  }
313  else
314  {
315  return "";
316  }
317  }
318 
319  template <typename T>
320  requires avnd::soundfile_port<T>
321  void operator()(const T& in, auto idx)
322  {
323  constexpr auto name = avnd::get_name<T>();
324  Process::FileChooserBase* p{};
325  if constexpr(requires { T::waveform; })
326  {
327  p = new Process::AudioFileChooser{
328  "", getFilters(in), QString::fromUtf8(name.data(), name.size()),
329  Id<Process::Port>(inlet++), &self};
330  }
331  else
332  {
333  p = new Process::FileChooser{
334  "", getFilters(in), QString::fromUtf8(name.data(), name.size()),
335  Id<Process::Port>(inlet++), &self};
336  }
337 
338  p->hidden = true;
339  ins.push_back(p);
340 
341  if constexpr(avnd::tag_file_watch<T>)
342  {
343  p->enableFileWatch();
344  }
345  }
346 
347  template <typename T>
348  requires avnd::midifile_port<T> || avnd::raw_file_port<T>
349  void operator()(const T& in, auto idx)
350  {
351  constexpr auto name = avnd::get_name<T>();
352 
353  auto p = new Process::FileChooser{
354  "", getFilters(in), QString::fromUtf8(name.data(), name.size()),
355  Id<Process::Port>(inlet++), &self};
356  p->hidden = true;
357  ins.push_back(p);
358 
359  if constexpr(avnd::tag_file_watch<T>)
360  {
361  p->enableFileWatch();
362  }
363  }
364 
365  template <avnd::parameter T, std::size_t N>
366  void operator()(const T& in, avnd::field_index<N>)
367  {
368  if constexpr(avnd::control<T> || avnd::curve_port<T>)
369  {
370  auto p = oscr::make_control_in<Node, T>(
371  avnd::field_index<N>{}, Id<Process::Port>(inlet), &self);
372  if constexpr(!std::is_same_v<std::decay_t<decltype(p)>, std::nullptr_t>)
373  {
374  p->hidden = true;
375  ins.push_back(p);
376  }
377  else
378  {
379  auto vp = new Process::ValueInlet(Id<Process::Port>(inlet), &self);
380  setupNewPort(in, vp);
381  ins.push_back(vp);
382  }
383  }
384  else
385  {
386  auto vp = new Process::ValueInlet(Id<Process::Port>(inlet), &self);
387  setupNewPort(in, vp);
388  ins.push_back(vp);
389  }
390  inlet++;
391  }
392 
393 #if SCORE_PLUGIN_GFX
394  void operator()(const avnd::texture_port auto& in, auto idx)
395  {
396  auto p = new Gfx::TextureInlet(Id<Process::Port>(inlet++), &self);
397  setupNewPort(in, p);
398  ins.push_back(p);
399  }
400  void operator()(const avnd::geometry_port auto& in, auto idx)
401  {
402  auto p = new Gfx::GeometryInlet(Id<Process::Port>(inlet++), &self);
403  setupNewPort(in, p);
404  ins.push_back(p);
405  }
406 #endif
407 
408  template <std::size_t Idx, avnd::message T>
409  void operator()(const avnd::field_reflection<Idx, T>& in)
410  {
411  auto p = new Process::ValueInlet(Id<Process::Port>(inlet++), &self);
412  setupNewPort(in, p);
413  ins.push_back(p);
414  }
415 
416  template <std::size_t Idx, avnd::unreflectable_message<Node> T>
417  void operator()(const avnd::field_reflection<Idx, T>& in)
418  {
419  auto p = new Process::ValueInlet(Id<Process::Port>(inlet++), &self);
420  setupNewPort(in, p);
421  ins.push_back(p);
422  }
423 
424  void operator()(const auto& ctrl, auto idx)
425  {
426  //(avnd::message<std::decay_t<decltype(ctrl)>>);
427  qDebug() << fromStringView(avnd::get_name(ctrl)) << "unhandled";
428  }
429 };
430 
431 template <typename Node>
433 {
434  Process::ProcessModel& self;
435  Process::Outlets& outs;
436  int outlet = 0;
437 
438  void operator()(const avnd::audio_port auto& out, auto idx)
439  {
440  auto p = new Process::AudioOutlet(Id<Process::Port>(outlet++), &self);
441  setupNewPort(out, p);
442  if(outlet == 1)
443  p->setPropagate(true);
444  outs.push_back(p);
445  }
446 
447  void operator()(const avnd::midi_port auto& out, auto idx)
448  {
449  auto p = new Process::MidiOutlet(Id<Process::Port>(outlet++), &self);
450  setupNewPort(out, p);
451  outs.push_back(p);
452  }
453 
454  template <avnd::parameter T, std::size_t N>
455  void operator()(const T& out, avnd::field_index<N>)
456  {
457  if constexpr(avnd::control<T>)
458  {
459  if(auto p = oscr::make_control_out<T>(
460  avnd::field_index<N>{}, Id<Process::Port>(outlet), &self))
461  {
462  p->hidden = true;
463  outs.push_back(p);
464  }
465  else
466  {
467  auto vp = new Process::ValueOutlet(Id<Process::Port>(outlet), &self);
468  setupNewPort(out, vp);
469  outs.push_back(vp);
470  }
471  }
472  else
473  {
474  auto vp = new Process::ValueOutlet(Id<Process::Port>(outlet), &self);
475  setupNewPort(out, vp);
476  outs.push_back(vp);
477  }
478  outlet++;
479  }
480 
481 #if SCORE_PLUGIN_GFX
482  void operator()(const avnd::texture_port auto& out, auto idx)
483  {
484  auto p = new Gfx::TextureOutlet(Id<Process::Port>(outlet++), &self);
485  setupNewPort(out, p);
486  outs.push_back(p);
487  }
488  void operator()(const avnd::geometry_port auto& out, auto idx)
489  {
490  auto p = new Gfx::GeometryOutlet(Id<Process::Port>(outlet++), &self);
491  setupNewPort(out, p);
492  outs.push_back(p);
493  }
494 #endif
495 
496  void operator()(const avnd::curve_port auto& out, auto idx)
497  {
498  auto p = new Process::ValueOutlet(Id<Process::Port>(outlet++), &self);
499  setupNewPort(out, p);
500  outs.push_back(p);
501  }
502 
503  void operator()(const avnd::callback auto& out, auto idx)
504  {
505  auto p = new Process::ValueOutlet(Id<Process::Port>(outlet++), &self);
506  setupNewPort(out, p);
507  outs.push_back(p);
508  }
509 
510  void operator()(const auto& ctrl, auto idx)
511  {
512  qDebug() << fromStringView(avnd::get_name(ctrl)) << "unhandled";
513  }
514 };
515 
516 template <typename Info>
518 {
519 };
520 
521 template <typename Info>
523 {
524 };
525 
526 namespace
527 {
528 struct dummy_ui_callback
529 {
530  void operator()(const QByteArray& arr) noexcept { }
531 };
532 }
533 
534 template <avnd::has_processor_to_gui_bus Info>
536 {
537  std::function<void(QByteArray)> to_ui = dummy_ui_callback{};
538 };
539 
540 template <avnd::has_gui_to_processor_bus Info>
542 {
543  std::function<void(QByteArray)> from_ui = dummy_ui_callback{};
544 };
545 
546 template <typename Info>
547 class ProcessModel final
548  : public Process::ProcessModel
549  , public MessageBusWrapperFromUi<Info>
550  , public MessageBusWrapperToUi<Info>
551 {
552  SCORE_SERIALIZE_FRIENDS
553  PROCESS_METADATA_IMPL(ProcessModel<Info>)
554  friend struct TSerializer<DataStream, oscr::ProcessModel<Info>>;
555  friend struct TSerializer<JSONObject, oscr::ProcessModel<Info>>;
556 
557 public:
558  ProcessModel(
559  const TimeVal& duration, const Id<Process::ProcessModel>& id, QObject* parent)
561  duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
562  {
563  metadata().setInstanceName(*this);
564 
565  avnd::port_visit_dispatcher<Info>(
566  InletInitFunc<Info>{*this, m_inlets}, OutletInitFunc<Info>{*this, m_outlets});
567  }
568  ProcessModel(
569  const TimeVal& duration, const QString& custom,
570  const Id<Process::ProcessModel>& id, QObject* parent)
572  duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
573  {
574  metadata().setInstanceName(*this);
575 
576  avnd::port_visit_dispatcher<Info>(
577  InletInitFunc<Info>{*this, m_inlets}, OutletInitFunc<Info>{*this, m_outlets});
578 
579  if constexpr(avnd::file_input_introspection<Info>::size > 0)
580  {
581  constexpr auto idx = avnd::file_input_introspection<Info>::index_to_field_index(0);
582  setupInitialStringPort(idx, custom);
583  }
584  else if constexpr(avnd::control_input_introspection<Info>::size > 0)
585  {
586  constexpr auto idx
587  = avnd::control_input_introspection<Info>::index_to_field_index(0);
588  using type =
589  typename avnd::control_input_introspection<Info>::template nth_element<0>;
590  if constexpr(avnd::string_ish<decltype(type::value)>)
591  setupInitialStringPort(idx, custom);
592  }
593  }
594 
595  void setupInitialStringPort(int idx, const QString& custom) noexcept
596  {
597  Process::Inlet* port = modelPort<Info>(this->inlets(), idx);
598  auto pp = safe_cast<Process::ControlInlet*>(port);
599 
600  if(pp->value().target<std::string>())
601  {
602  pp->setValue(custom.toStdString());
603  }
604  }
605 
606  template <typename Impl>
607  explicit ProcessModel(Impl& vis, QObject* parent)
608  : Process::ProcessModel{vis, parent}
609  {
610  vis.writeTo(*this);
611  }
612 
613  ~ProcessModel() override { }
614 };
615 }
616 
617 template <typename Info>
618 struct is_custom_serialized<oscr::ProcessModel<Info>> : std::true_type
619 {
620 };
621 
622 template <typename Info>
623 struct TSerializer<DataStream, oscr::ProcessModel<Info>>
624 {
626  static void readFrom(DataStream::Serializer& s, const model_type& obj)
627  {
628  Process::readPorts(s, obj.m_inlets, obj.m_outlets);
629  s.insertDelimiter();
630  }
631 
632  static void writeTo(DataStream::Deserializer& s, model_type& obj)
633  {
634  Process::writePorts(
635  s, s.components.interfaces<Process::PortFactoryList>(), obj.m_inlets,
636  obj.m_outlets, &obj);
637  s.checkDelimiter();
638  }
639 };
640 
641 template <typename Info>
642 struct TSerializer<JSONObject, oscr::ProcessModel<Info>>
643 {
645  static void readFrom(JSONObject::Serializer& s, const model_type& obj)
646  {
647  Process::readPorts(s, obj.m_inlets, obj.m_outlets);
648  }
649 
650  static void writeTo(JSONObject::Deserializer& s, model_type& obj)
651  {
652  Process::writePorts(
653  s, s.components.interfaces<Process::PortFactoryList>(), obj.m_inlets,
654  obj.m_outlets, &obj);
655  }
656 };
Metadata to categorize objects: curves, audio, etc.
Definition: lib/score/tools/Metadata.hpp:61
Definition: VisitorInterface.hpp:53
Definition: DataStreamVisitor.hpp:27
Definition: DataStreamVisitor.hpp:202
Definition: TexturePort.hpp:121
Definition: TexturePort.hpp:144
Definition: VisitorInterface.hpp:61
Definition: JSONVisitor.hpp:52
Definition: JSONVisitor.hpp:423
The DropHandler class If something with audio mime type is dropped, then we create a box with an audi...
Definition: SoundDrop.hpp:26
Metadata to get the key part of ObjectIdentifier.
Definition: lib/score/tools/Metadata.hpp:36
Metadata to get the name that will be shown in the user interface.
Definition: lib/score/tools/Metadata.hpp:42
Definition: score-lib-process/Process/Dataflow/Port.hpp:290
Definition: score-lib-process/Process/Dataflow/Port.hpp:313
Definition: score-lib-process/Process/Dataflow/Port.hpp:176
Definition: score-lib-process/Process/Dataflow/Port.hpp:369
Definition: score-lib-process/Process/Dataflow/Port.hpp:392
Definition: PortFactory.hpp:65
Definition: score-lib-process/Process/Dataflow/Port.hpp:102
The Process class.
Definition: score-lib-process/Process/Process.hpp:61
Definition: score-lib-process/Process/Dataflow/Port.hpp:481
Definition: score-lib-process/Process/Dataflow/Port.hpp:504
Metadata to retrieve the ProcessFlags of a process.
Metadata to associate tags to objects.
Definition: lib/score/tools/Metadata.hpp:67
The id_base_t class.
Definition: Identifier.hpp:57
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:551
Base classes and tools to implement processes and layers.
Definition: JSONVisitor.hpp:1324
ProcessFlags
Various settings for processes.
Definition: ProcessFlags.hpp:17
Static metadata implementation.
Definition: lib/score/tools/Metadata.hpp:36
Definition: score-lib-process/Process/ProcessMetadata.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:243
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:523
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:518
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:433