Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
audio_route_catalog.hpp
Go to the documentation of this file.
1#pragma once
2
3// Host-owned audio source discovery. A plug-in never enumerates a DAW's
4// project tracks from its realtime processor; the host supplies the catalog
5// and connects one of its entries to a declared input bus.
6
7#include <algorithm>
8#include <array>
9#include <cstddef>
10#include <cstdint>
11#include <functional>
12#include <limits>
13#include <span>
14#include <string>
15#include <string_view>
16#include <utility>
17#include <vector>
18
19namespace pulp::host {
20
29
37
43 public:
44 using ChangeCallback = std::function<void()>;
45 using ConnectCallback = std::function<bool(const AudioRouteInfo&, std::size_t bus_index)>;
46 using ConnectChannelCallback = std::function<bool(const AudioRouteInfo&, std::size_t bus_index,
47 std::size_t channel_index)>;
48 using DisconnectCallback = std::function<bool(std::size_t bus_index)>;
49
50 virtual ~AudioRouteCatalog() = default;
51 virtual std::vector<AudioRouteInfo> enumerate() const = 0;
52 virtual void set_change_callback(ChangeCallback callback) = 0;
53
58 virtual void set_connect_callback(ConnectCallback callback) {
59 (void)callback;
60 }
61
65 (void)callback;
66 }
67
71 (void)callback;
72 }
73
77 virtual bool connect(std::string_view stable_id, std::size_t bus_index) {
78 (void)stable_id;
79 (void)bus_index;
80 return false;
81 }
82
85 virtual bool connect_channel(std::string_view stable_id, std::size_t bus_index,
86 std::size_t channel_index) {
87 (void)channel_index;
88 return connect(stable_id, bus_index);
89 }
90
91 virtual bool disconnect(std::size_t bus_index) {
92 (void)bus_index;
93 return false;
94 }
95
98 virtual std::string connected_route(std::size_t bus_index) const {
99 (void)bus_index;
100 return {};
101 }
102
105 virtual std::string connected_route_channel(std::size_t bus_index,
106 std::size_t channel_index) const {
107 (void)channel_index;
108 return connected_route(bus_index);
109 }
110
115 virtual std::vector<std::string> selected_route_ids() const {
116 return {};
117 }
118
122 virtual void restore_selected_route_ids(std::span<const std::string> ids) {
123 (void)ids;
124 }
125};
126
131 public:
132 std::vector<AudioRouteInfo> enumerate() const override {
133 return routes_;
134 }
135
136 void set_routes(std::vector<AudioRouteInfo> routes) {
137 // Preserve selections whose stable IDs still exist, including routes
138 // that are temporarily unavailable. Hosts commonly rebuild the
139 // catalog for hot-plug/project edits; retaining the stable identity
140 // lets the picker show an unavailable checkmarked source and lets the
141 // host reconnect it when it returns. The graph binding remains the
142 // authority for whether audio can currently flow.
143 std::vector<std::string> retained;
144 retained.reserve(selected_.size());
145 for (const auto& selected : selected_) {
146 // Keep the ID even when the route is absent from this snapshot.
147 // A physical device or project track can disappear for one
148 // refresh and return later; clearing it here loses the user's
149 // selection and makes state restore impossible. `connect()` still
150 // rejects absent/unavailable routes, so this is only identity,
151 // never an audio-path claim.
152 retained.push_back(selected);
153 }
154 std::vector<std::array<std::string, 2>> retained_channels;
155 retained_channels.reserve(selected_channels_.size());
156 for (const auto& selected : selected_channels_)
157 retained_channels.push_back(selected);
158 routes_ = std::move(routes);
159 selected_ = std::move(retained);
160 selected_channels_ = std::move(retained_channels);
161 if (connect_channel_callback_) {
162 for (std::size_t bus_index = 0; bus_index < selected_channels_.size(); ++bus_index) {
163 for (std::size_t channel_index = 0; channel_index < 2; ++channel_index) {
164 const auto& selected = selected_channels_[bus_index][channel_index];
165 if (selected.empty())
166 continue;
167 const auto it = std::find_if(
168 routes_.begin(), routes_.end(), [&](const AudioRouteInfo& route) {
169 return route.stable_id == selected && route.available &&
170 channel_index < static_cast<std::size_t>(route.channels);
171 });
172 if (it != routes_.end())
173 (void)connect_channel_callback_(*it, bus_index, channel_index);
174 }
175 }
176 } else if (connect_callback_) {
177 for (std::size_t bus_index = 0; bus_index < selected_.size(); ++bus_index) {
178 const auto it =
179 std::find_if(routes_.begin(), routes_.end(), [&](const AudioRouteInfo& route) {
180 return route.stable_id == selected_[bus_index] && route.available &&
181 route.channels > 0;
182 });
183 if (it != routes_.end())
184 (void)connect_callback_(*it, bus_index);
185 }
186 }
187 notify();
188 }
189
190 void set_change_callback(ChangeCallback callback) override {
191 callback_ = std::move(callback);
192 }
193
194 void set_connect_callback(ConnectCallback callback) override {
195 connect_callback_ = std::move(callback);
196 }
197
199 connect_channel_callback_ = std::move(callback);
200 }
201
203 disconnect_callback_ = std::move(callback);
204 }
205
206 bool connect(std::string_view stable_id, std::size_t bus_index) override {
207 if (bus_index == std::numeric_limits<std::size_t>::max() ||
208 bus_index >= selected_.max_size())
209 return false;
210 const auto it =
211 std::find_if(routes_.begin(), routes_.end(), [stable_id](const AudioRouteInfo& route) {
212 return route.stable_id == stable_id && route.available && route.channels > 0;
213 });
214 if (it == routes_.end())
215 return false;
216 ensure_bus(bus_index);
217 const bool feeds_right = it->channels > 1;
218 if (connect_channel_callback_) {
219 // A whole-bus binding replaces both channels. A source that cannot
220 // feed the right channel must first drop any independent right
221 // binding, which only the bus-level disconnect can do.
222 const bool stale_right = !selected_channels_[bus_index][1].empty() && !feeds_right;
223 if (stale_right) {
224 if (!disconnect_callback_ || !disconnect_callback_(bus_index))
225 return false;
226 selected_[bus_index].clear();
227 selected_channels_[bus_index] = {};
228 }
229 if (!connect_channel_callback_(*it, bus_index, 0)) {
230 if (stale_right)
231 notify();
232 return false;
233 }
234 selected_[bus_index] = it->stable_id;
235 selected_channels_[bus_index][0] = it->stable_id;
236 if (!feeds_right) {
237 selected_channels_[bus_index][1].clear();
238 } else if (connect_channel_callback_(*it, bus_index, 1)) {
239 selected_channels_[bus_index][1] = it->stable_id;
240 }
241 notify();
242 return !feeds_right || !selected_channels_[bus_index][1].empty();
243 }
244 if (connect_callback_ && !connect_callback_(*it, bus_index))
245 return false;
246 set_whole_bus(bus_index, *it);
247 notify();
248 return true;
249 }
250
251 bool connect_channel(std::string_view stable_id, std::size_t bus_index,
252 std::size_t channel_index) override {
253 if (bus_index == std::numeric_limits<std::size_t>::max() ||
254 bus_index >= selected_.max_size() || channel_index >= 2)
255 return false;
256 const auto it =
257 std::find_if(routes_.begin(), routes_.end(), [stable_id](const AudioRouteInfo& route) {
258 return route.stable_id == stable_id && route.available && route.channels > 0;
259 });
260 if (it == routes_.end())
261 return false;
262 if (channel_index >= static_cast<std::size_t>(it->channels))
263 return false;
264 if (connect_channel_callback_) {
265 if (!connect_channel_callback_(*it, bus_index, channel_index))
266 return false;
267 } else if (connect_callback_ && channel_index == 0) {
268 // The legacy callback rebinds the whole bus, so the catalog must
269 // record a whole-bus selection rather than a lone left channel.
270 if (!connect_callback_(*it, bus_index))
271 return false;
272 ensure_bus(bus_index);
273 set_whole_bus(bus_index, *it);
274 notify();
275 return true;
276 } else if (connect_callback_) {
277 // A legacy whole-bus callback cannot bind an independent channel.
278 return false;
279 }
280 ensure_bus(bus_index);
281 selected_channels_[bus_index][channel_index] = it->stable_id;
282 if (channel_index == 0)
283 selected_[bus_index] = it->stable_id;
284 notify();
285 return true;
286 }
287
288 bool disconnect(std::size_t bus_index) override {
289 const bool bus_selected = bus_index < selected_.size() && !selected_[bus_index].empty();
290 const bool channel_selected =
291 bus_index < selected_channels_.size() &&
292 std::any_of(
293 selected_channels_[bus_index].begin(), selected_channels_[bus_index].end(),
294 [](const std::string& selected) { return !selected.empty(); });
295 if (!bus_selected && !channel_selected)
296 return false;
297 if (disconnect_callback_ && !disconnect_callback_(bus_index))
298 return false;
299 if (bus_index < selected_.size())
300 selected_[bus_index].clear();
301 if (bus_index < selected_channels_.size())
302 selected_channels_[bus_index] = {};
303 notify();
304 return true;
305 }
306
307 std::string connected_route(std::size_t bus_index) const override {
308 return bus_index < selected_.size() ? selected_[bus_index] : std::string{};
309 }
310
311 std::string connected_route_channel(std::size_t bus_index,
312 std::size_t channel_index) const override {
313 if (bus_index >= selected_channels_.size() || channel_index >= 2)
314 return {};
315 return selected_channels_[bus_index][channel_index];
316 }
317
318 std::vector<std::string> selected_route_ids() const override {
319 return selected_;
320 }
321
322 void restore_selected_route_ids(std::span<const std::string> ids) override {
323 for (std::size_t bus_index = 0; bus_index < ids.size(); ++bus_index) {
324 if (!ids[bus_index].empty())
325 (void)connect(ids[bus_index], bus_index);
326 }
327 }
328
329 private:
330 void ensure_bus(std::size_t bus_index) {
331 if (selected_.size() <= bus_index)
332 selected_.resize(bus_index + 1);
333 if (selected_channels_.size() <= bus_index)
334 selected_channels_.resize(bus_index + 1);
335 }
336
337 void set_whole_bus(std::size_t bus_index, const AudioRouteInfo& route) {
338 selected_[bus_index] = route.stable_id;
339 selected_channels_[bus_index][0] = route.stable_id;
340 selected_channels_[bus_index][1] = route.channels > 1 ? route.stable_id : std::string{};
341 }
342
343 void notify() {
344 if (callback_)
345 callback_();
346 }
347
348 std::vector<AudioRouteInfo> routes_;
349 std::vector<std::string> selected_;
350 std::vector<std::array<std::string, 2>> selected_channels_;
351 ChangeCallback callback_;
352 ConnectCallback connect_callback_;
353 ConnectChannelCallback connect_channel_callback_;
354 DisconnectCallback disconnect_callback_;
355};
356
357} // namespace pulp::host
Control-thread source catalog supplied by a Pulp host (standalone, Forge, or another graph host).
Definition audio_route_catalog.hpp:42
virtual std::string connected_route(std::size_t bus_index) const
Return the stable source currently connected to a bus, or empty when the host has not selected one.
Definition audio_route_catalog.hpp:98
virtual void set_connect_callback(ConnectCallback callback)
Install the host graph/bus binding invoked by connect().
Definition audio_route_catalog.hpp:58
std::function< bool(std::size_t bus_index)> DisconnectCallback
Definition audio_route_catalog.hpp:48
virtual void set_disconnect_callback(DisconnectCallback callback)
Install the host graph binding for disconnect().
Definition audio_route_catalog.hpp:70
virtual std::vector< std::string > selected_route_ids() const
Return the stable IDs selected for each declared input bus.
Definition audio_route_catalog.hpp:115
virtual bool disconnect(std::size_t bus_index)
Definition audio_route_catalog.hpp:91
virtual void restore_selected_route_ids(std::span< const std::string > ids)
Restore previously saved stable IDs on the control thread.
Definition audio_route_catalog.hpp:122
virtual bool connect(std::string_view stable_id, std::size_t bus_index)
Connect a host-owned source to a declared processor input bus.
Definition audio_route_catalog.hpp:77
std::function< void()> ChangeCallback
Definition audio_route_catalog.hpp:44
virtual void set_change_callback(ChangeCallback callback)=0
virtual std::vector< AudioRouteInfo > enumerate() const =0
virtual ~AudioRouteCatalog()=default
std::function< bool(const AudioRouteInfo &, std::size_t bus_index, std::size_t channel_index)> ConnectChannelCallback
Definition audio_route_catalog.hpp:46
std::function< bool(const AudioRouteInfo &, std::size_t bus_index)> ConnectCallback
Definition audio_route_catalog.hpp:45
virtual void set_connect_channel_callback(ConnectChannelCallback callback)
Install the host graph binding for an individual channel connection.
Definition audio_route_catalog.hpp:64
virtual std::string connected_route_channel(std::size_t bus_index, std::size_t channel_index) const
Return the stable source currently connected to one bus channel.
Definition audio_route_catalog.hpp:105
virtual bool connect_channel(std::string_view stable_id, std::size_t bus_index, std::size_t channel_index)
Connect one channel of a declared input bus.
Definition audio_route_catalog.hpp:85
Small host-side implementation for standalone/Forge hosts.
Definition audio_route_catalog.hpp:130
std::string connected_route_channel(std::size_t bus_index, std::size_t channel_index) const override
Return the stable source currently connected to one bus channel.
Definition audio_route_catalog.hpp:311
void set_change_callback(ChangeCallback callback) override
Definition audio_route_catalog.hpp:190
bool connect_channel(std::string_view stable_id, std::size_t bus_index, std::size_t channel_index) override
Connect one channel of a declared input bus.
Definition audio_route_catalog.hpp:251
void set_connect_callback(ConnectCallback callback) override
Install the host graph/bus binding invoked by connect().
Definition audio_route_catalog.hpp:194
void set_disconnect_callback(DisconnectCallback callback) override
Install the host graph binding for disconnect().
Definition audio_route_catalog.hpp:202
void restore_selected_route_ids(std::span< const std::string > ids) override
Restore previously saved stable IDs on the control thread.
Definition audio_route_catalog.hpp:322
std::vector< AudioRouteInfo > enumerate() const override
Definition audio_route_catalog.hpp:132
bool disconnect(std::size_t bus_index) override
Definition audio_route_catalog.hpp:288
std::vector< std::string > selected_route_ids() const override
Return the stable IDs selected for each declared input bus.
Definition audio_route_catalog.hpp:318
std::string connected_route(std::size_t bus_index) const override
Return the stable source currently connected to a bus, or empty when the host has not selected one.
Definition audio_route_catalog.hpp:307
void set_routes(std::vector< AudioRouteInfo > routes)
Definition audio_route_catalog.hpp:136
bool connect(std::string_view stable_id, std::size_t bus_index) override
Connect a host-owned source to a declared processor input bus.
Definition audio_route_catalog.hpp:206
void set_connect_channel_callback(ConnectChannelCallback callback) override
Install the host graph binding for an individual channel connection.
Definition audio_route_catalog.hpp:198
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
AudioRouteKind
Definition audio_route_catalog.hpp:21
@ File
Definition audio_route_catalog.hpp:26
@ Instrument
Definition audio_route_catalog.hpp:24
@ Bus
Definition audio_route_catalog.hpp:25
@ Track
Definition audio_route_catalog.hpp:23
@ Internal
Definition audio_route_catalog.hpp:27
@ PhysicalInput
Definition audio_route_catalog.hpp:22
Definition audio_route_catalog.hpp:30
std::string stable_id
Definition audio_route_catalog.hpp:31
std::string display_name
Definition audio_route_catalog.hpp:32
bool available
Definition audio_route_catalog.hpp:35
int channels
Definition audio_route_catalog.hpp:34
AudioRouteKind kind
Definition audio_route_catalog.hpp:33