Loading...
Searching...
No Matches
ValueDisplay.hpp
1#pragma once
2#include <Process/Dataflow/Port.hpp>
3#include <Process/Dataflow/PortFactory.hpp>
4#include <Process/Dataflow/PortItem.hpp>
5#include <Process/Process.hpp>
6
7#include <Effect/EffectLayer.hpp>
8#include <Effect/EffectLayout.hpp>
9
10#include <score/application/ApplicationContext.hpp>
11#include <score/model/Skin.hpp>
12
13#include <ossia/dataflow/port.hpp>
14#include <ossia/network/value/format_value.hpp>
15#include <ossia/network/value/value_conversion.hpp>
16
17#include <boost/container/devector.hpp>
18
19#include <QApplication>
20#include <QClipboard>
21#include <QMenu>
22#include <QPainter>
23
24#include <halp/audio.hpp>
25#include <halp/controls.hpp>
26#include <halp/meta.hpp>
27#include <halp/polyfill.hpp>
28#include <halp/static_string.hpp>
29
30#include <algorithm>
31#include <iterator>
32#include <optional>
33#include <vector>
34
35namespace Ui::ValueDisplay
36{
37template <halp::static_string lit, typename T>
39{
40 halp_meta(is_event, true)
41 static clang_buggy_consteval auto name() { return std::string_view{lit.value}; }
42
43 T* value{};
44};
45
46struct Node
47{
48 halp_meta(name, "Value display")
49 halp_meta(c_name, "Display")
50 halp_meta(category, "Monitoring")
51 halp_meta(author, "ossia score")
52 halp_meta(manual_url, "")
53 halp_meta(description, "Visualize an input value")
54 halp_meta(uuid, "3f4a41f2-fa39-420f-ab0f-0af6b8409edb")
55 halp_flag(fully_custom_item);
56
57 static constexpr int max_log = 100;
58
59 struct
60 {
61 // Reading the port directly rather than through a control input: a control
62 // input is fed with get_data().back(), so only the last value of a tick
63 // would ever be seen, and a burst - a note-off immediately followed by a
64 // note-on, several values written at the same sample - would show up as a
65 // single entry.
66 raw_port<"in", ossia::value_port> port;
67
68 halp::spinbox_i32<"Log", halp::range{1, max_log, 1}> log;
69 } inputs;
70
71 struct
72 {
73 // [sequence number of the first entry, values...]
74 struct : halp::val_port<"values", std::optional<ossia::value>>
75 {
76 enum widget
77 {
78 control
79 };
80 } values;
81 } outputs;
82
83 // The engine -> UI queue for control outputs is drained keeping only the last
84 // entry, so a push has to carry everything the layer may still need. That is
85 // never more than the log length, which bounds the ring exactly.
86 boost::container::devector<ossia::value> m_ring;
87 int m_next_seq = 0;
88
89 void prepare(halp::setup) noexcept
90 {
91 m_ring.clear();
92 m_next_seq = 0;
93 }
94
95 void operator()()
96 {
97 outputs.values.value.reset();
98
99 if(!inputs.port.value)
100 return;
101
102 const auto& data = inputs.port.value->get_data();
103 if(data.empty())
104 return;
105
106 // Never more than what the layer displays: with a log of 1 this sends the
107 // last value and nothing else.
108 const int keep = std::clamp(inputs.log.value, 1, max_log);
109
110 for(const ossia::timed_value& tv : data)
111 {
112 m_ring.push_back(tv.value);
113 m_next_seq++;
114 if(std::ssize(m_ring) > keep)
115 m_ring.pop_front();
116 }
117 while(std::ssize(m_ring) > keep)
118 m_ring.pop_front();
119
120 std::vector<ossia::value> payload;
121 payload.reserve(1 + m_ring.size());
122 payload.push_back(int(m_next_seq - std::ssize(m_ring)));
123 for(const auto& v : m_ring)
124 payload.push_back(v);
125
126 outputs.values.value = ossia::value{std::move(payload)};
127 }
128
130 {
131 public:
132 boost::container::devector<ossia::value> values;
133 int m_next_seq = 0;
134
135 Process::ControlInlet* log_inlet{};
136
137 mutable QString txt_cache;
138
139 int logging() const noexcept
140 {
141 return std::clamp(ossia::convert<int>(log_inlet->value()), 1, max_log);
142 }
143
144 Layer(
145 const Process::ProcessModel& process, const Process::Context& doc,
146 QGraphicsItem* parent)
148 {
149 setAcceptedMouseButtons(Qt::NoButton);
150
151 const Process::PortFactoryList& portFactory
153
154 auto& value_inlet = *process.inlets()[0];
155 if(auto* fact = portFactory.get(value_inlet.concreteKey()))
156 if(auto* port = fact->makePortItem(value_inlet, doc, this, this))
157 port->setPos(0, 5);
158
159 log_inlet = static_cast<Process::ControlInlet*>(process.inlets()[1]);
160
161 auto* out = static_cast<Process::ControlOutlet*>(process.outlets()[0]);
162 connect(
163 out, &Process::ControlOutlet::valueChanged, this,
164 [this](const ossia::value& v) { on_values(v); });
165
166 connect(
167 log_inlet, &Process::ControlInlet::valueChanged, this,
168 [this](const ossia::value& v) {
169 while(std::ssize(values) > logging())
170 values.pop_back();
171 update();
172 });
173 }
174
175 void on_values(const ossia::value& v)
176 {
177 auto* list = v.target<std::vector<ossia::value>>();
178 if(!list || list->size() < 2)
179 return;
180
181 const int base = ossia::convert<int>((*list)[0]);
182 const int n = std::ssize(*list) - 1;
183
184 // The batch is entirely older than what we hold: the process restarted.
185 if(base + n < m_next_seq)
186 {
187 values.clear();
188 m_next_seq = base;
189 }
190
191 for(int i = 0; i < n; i++)
192 {
193 const int seq = base + i;
194 if(seq < m_next_seq)
195 continue;
196 values.push_front((*list)[i + 1]);
197 m_next_seq = seq + 1;
198 }
199
200 while(std::ssize(values) > logging())
201 values.pop_back();
202
203 update();
204 }
205
206 void reset()
207 {
208 values.clear();
209 m_next_seq = 0;
210 update();
211 }
212
213 void paint_impl(QPainter* p) const override
214 {
215 if(values.empty())
216 return;
217
218 p->setRenderHint(QPainter::Antialiasing, true);
219 p->setPen(score::Skin::instance().Light.main.pen1_solid_flat_miter);
220
221 {
222 txt_cache.clear();
223 for(auto& line : this->values)
224 txt_cache += QString::fromStdString(fmt::format("{}\n", line));
225 p->drawText(boundingRect().adjusted(10, 0, 0, 0), txt_cache);
226 }
227
228 p->setRenderHint(QPainter::Antialiasing, false);
229 }
230 };
231
233 {
234 using Process::EffectLayerPresenter::EffectLayerPresenter;
235 void fillContextMenu(
236 QMenu& menu, QPoint pos, QPointF scenepos,
237 const Process::LayerContextMenuManager&) override
238 {
239 auto cp = menu.addAction(tr("Copy value"));
240 connect(cp, &QAction::triggered, this, [this] {
241 auto& v = *static_cast<Layer*>(this->m_view);
242 qApp->clipboard()->setText(v.txt_cache);
243 });
244 }
245 };
246};
247}
Definition Port.hpp:206
Definition Port.hpp:427
Definition EffectLayer.hpp:32
Definition EffectLayer.hpp:16
Definition LayerContextMenu.hpp:38
Definition PortFactory.hpp:82
The Process class.
Definition score-lib-process/Process/Process.hpp:62
FactoryType * get(const key_type &k) const noexcept
Get a particular factory from its ConcreteKey.
Definition InterfaceList.hpp:128
Definition ProcessContext.hpp:12
Definition ValueDisplay.hpp:130
Definition ValueDisplay.hpp:233
Definition ValueDisplay.hpp:47
Definition ValueDisplay.hpp:39
const T & interfaces() const
Access to a specific interface list.
Definition ApplicationContext.hpp:70