Sygaldry
Loading...
Searching...
No Matches
sygbp-liblo.hpp
1#pragma once
2/*
3Copyright 2023 Travis J. West, https://traviswest.ca, Input Devices and Music
4Interaction Laboratory (IDMIL), Centre for Interdisciplinary Research in Music
5Media and Technology (CIRMMT), McGill University, Montréal, Canada, and Univ.
6Lille, Inria, CNRS, Centrale Lille, UMR 9189 CRIStAL, F-59000 Lille, France
7
8SPDX-License-Identifier: LGPL-2.1-or-later
9*/
10
11#include <stdio.h>
12#include <charconv>
13#include <lo/lo.h>
14#include <lo/lo_lowlevel.h>
15#include <lo/lo_types.h>
16#include "sygac-metadata.hpp"
17#include "sygac-endpoints.hpp"
18#include "sygah-endpoints.hpp"
19#include "sygbp-osc_string_constants.hpp"
20
21namespace sygaldry { namespace sygbp {
26
27template<typename Components>
29: name_<"Liblo OSC">
30, author_<"Travis J. West">
31, copyright_<"Copyright 2023 Travis J. West">
32, license_<"SPDX-License-Identifier: LGPL-2.1-or-later">
33, version_<"0.0.0">
34, description_<"Open Sound Control bindings using the liblo library">
35{
36 struct inputs_t {
37 text_message< "source port"
38 , "The UDP port on which to receive incoming messages."
40 > src_port;
41 text_message< "destination port"
42 , "The UDP port on which to send outgoing messages."
44 > dst_port;
45 text_message< "destination address"
46 , "The IP address to send outgoing messages to."
48 > dst_addr;
49 } inputs;
50
51 struct outputs_t {
52 toggle<"server running"> server_running;
53 toggle<"output running"> output_running;
54 } outputs;
55
56 lo_server server{};
57 lo_address dst{};
58
59 template<typename T> static void
60 set_input(const char *path, const char *types
61 , lo_arg **argv, int argc, lo_message msg
62 , T& in
63 )
64 {
65 if constexpr (Bang<T>) in = true;
66 else if constexpr (std::integral<value_t<T>>)
67 {
68 if (types[0] != 'i')
69 {
70 fprintf(stderr, "liblo: wrong type; expected 'i', got '%c'\n", types[0]);
71 return;
72 }
73 set_value(in, argv[0]->i);
74 }
75 else if constexpr (std::floating_point<value_t<T>>)
76 {
77 if (types[0] != 'f')
78 {
79 fprintf(stderr, "liblo: wrong type; expected 'f', got '%c'\n", types[0]);
80 return;
81 }
82 set_value(in, argv[0]->f);
83 }
84 else if constexpr (string_like<value_t<T>>)
85 {
86 if (types[0] != 's')
87 {
88 fprintf(stderr, "liblo: wrong type; expected 's', got '%c'\n", types[0]);
89 return;
90 }
91 set_value(in, &argv[0]->s);
92 }
93 else if constexpr (array_like<value_t<T>>)
94 {
95 for (std::size_t i = 0; i < size<value_t<T>>(); ++i)
96 {
97 auto& element = value_of(in)[i];
98 if constexpr (std::integral<element_t<T>>)
99 {
100 if (types[i] != 'i')
101 {
102 fprintf(stderr, "liblo: wrong type; expected 'i', got '%c'\n", types[i]);
103 return;
104 }
105 element = argv[i]->i;
106 }
107 else if constexpr (std::floating_point<element_t<T>>)
108 {
109 if (types[i] != 'f')
110 {
111 fprintf(stderr, "liblo: wrong type; expected 'f', got '%c'\n", types[i]);
112 return;
113 }
114 element = argv[i]->f;
115 }
116 else if constexpr (string_like<element_t<T>>)
117 {
118 if (types[i] != 's')
119 {
120 fprintf(stderr, "liblo: wrong type; expected 's', got '%c'\n", types[i]);
121 return;
122 }
123 element = &argv[i]->s;
124 }
125 }
126 if constexpr (UpdatedFlag<T>) in.updated = true;
127 }
128 if constexpr (has_name<T>) fprintf(stdout, "liblo: set input %s\n", name_of(in));
129 else fprintf(stdout, "liblo: set unnamed input\n");
130 };
131
132 bool port_is_valid(auto& port)
133 {
134 int port_num = -1;
135 auto [ _, ec ] = std::from_chars(port->c_str(), port->c_str() + port->length(), port_num);
136 bool ret = ec == std::errc{} && (1024 <= port_num && port_num <= 65535);
137 if (not ret) fprintf(stderr, "liblo: invalid port %s (parsed as %d)\n", port->c_str(), port_num);
138 return ret;
139 }
140 void set_server(auto& components)
141 {
142 bool valid_user_src_port = port_is_valid(inputs.src_port);
143
144 // do not reset the server if the server is already running and the source port has not been validly updated
145 if (outputs.server_running && not (inputs.src_port.updated && valid_user_src_port)) return;
146
147 fprintf(stdout, "liblo: setting up server\n");
148 if (server) lo_server_free(server);
149
150 if (not valid_user_src_port)
151 {
152 fprintf(stdout, "liblo: searching for unused port\n");
153 server = lo_server_new(NULL, &LibloOsc::server_error_handler);
154 }
155 else
156 {
157 fprintf(stdout, "liblo: using given port %s\n", inputs.src_port->c_str());
158 server = lo_server_new(inputs.src_port->c_str(), &LibloOsc::server_error_handler);
159 }
160
161 if (server == NULL)
162 {
163 fprintf(stderr, "liblo: server setup failed\n");
164 outputs.server_running = 0;
165 return;
166 }
167
168 fprintf(stderr, "liblo: server setup successful\n");
169
170 if (not valid_user_src_port)
171 {
172 char port_str[6] = {0};
173 int port_num = lo_server_get_port(server);
174 snprintf(port_str, 6, "%d", port_num);
175 inputs.src_port.value() = port_str;
176 clear_flag(inputs.src_port); // clear flag to avoid triggering set_server again on tick
177 fprintf(stdout, "liblo: connected on port %s\n", inputs.src_port->c_str());
178 }
179 else
180 fprintf(stdout, "liblo: connected on port %s\n", inputs.src_port->c_str());
181
182 fprintf(stderr, "liblo: registering callbacks\n");
183 for_each_input(components, [&]<typename T>(T& in)
184 {
185 fprintf(stdout, "liblo: registering callback for %s ... ", name_of(in));
186 constexpr auto handler = +[]( const char *path, const char *types
187 , lo_arg **argv, int argc, lo_message msg
188 , void *user_data
189 )
190 {
191 #ifndef NDEBUG
192 fprintf(stdout, "liblo: got message %s", path);
193 lo_message_pp(msg);
194 #endif
195 T& in = *(T*)user_data;
196 LibloOsc::set_input(path, types, argv, argc, msg, in);
197 return 0;
198 };
199 lo_server_add_method( server
200 , osc_path_v<T, Components>, osc_type_string_v<T>+1
201 , handler, (void*)&in
202 );
203 fprintf(stdout, "done\n");
204 });
205 fprintf(stderr, "liblo: done registering callbacks\n");
206
207 outputs.server_running = 1;
208 return;
209 }
210
211 bool ip_addr_is_valid(auto& ip)
212 {
213 // TODO: implement a more robust verification of the dst ip address
214 bool ret = ip->length() >= 7;
215 if (not ret) fprintf(stderr, "liblo: invalid IP address %s\n", ip->c_str());
216 return ret;
217 }
218
219 bool dst_inputs_are_valid()
220 {
221 return ip_addr_is_valid(inputs.dst_addr) and port_is_valid(inputs.dst_port);
222 }
223 void set_dst()
224 {
225 bool dst_updated = (inputs.dst_port.updated || inputs.dst_addr.updated);
226 if (not (dst_updated && dst_inputs_are_valid()) ) return;
227 fprintf(stdout, "liblo: setting destination address to %s:%s\n"
228 , inputs.dst_addr->c_str(), inputs.dst_port->c_str()
229 );
230 if (dst) lo_address_free(dst);
231 dst = lo_address_new(inputs.dst_addr->c_str(), inputs.dst_port->c_str());
232 if (dst) outputs.output_running = 1;
233 else
234 {
235 outputs.output_running = 0;
236 fprintf(stderr, "liblo: unable to set destination address\n");
237 }
238 }
239
240 static void server_error_handler(int num, const char *msg, const char *where)
241 {
242 fprintf(stderr, "liblo error: %s %s\n", msg, where);
243 }
244
245 void init(Components& components)
246 {
247 fprintf(stdout, "liblo: initializing\n");
248 outputs.server_running = 0; // so that set_server doesn't short circuit immediately
249 set_server(components);
250 outputs.output_running = 0;
251 set_dst();
252 }
253
254 void external_sources()
255 {
256 if (outputs.server_running) lo_server_recv_noblock(server, 0);
257 }
258 void main(Components& components)
259 {
260 set_server(components);
261 set_dst();
262 }
263 void external_destinations(Components& components)
264 {
265 if (outputs.output_running)
266 {
267 lo_bundle bundle = lo_bundle_new(LO_TT_IMMEDIATE);
268 for_each_output(components, [&]<typename T>(T& output)
269 {
270 if constexpr (OccasionalValue<T> || Bang<T>)
271 {
272 if (not flag_state_of(output))
273 return;
274 }
275 else if constexpr (requires (T t) {t == output;})
276 {
277 static T prev{};
278 if (output == prev)
279 return;
280 prev = output;
281 }
282
283 lo_message message = lo_message_new();
284 if (!message)
285 {
286 perror("liblo: unable to malloc new message. perror reports: \n");
287 return;
288 }
289 if constexpr (has_value<T> && not Bang<T>)
290 {
291 int ret = 0;
292 // TODO: this type tag string logic should be moved to osc_string_constants.lili.md
293 constexpr auto type = std::integral<element_t<T>> ? "i"
294 : std::floating_point<element_t<T>> ? "f"
295 : string_like<element_t<T>> ? "s" : "" ;
296 // TODO: we should have a more generic way to get a char * from a string_like value
297 if constexpr (string_like<value_t<T>>)
298 ret = lo_message_add_string(message, value_of(output).c_str());
299 else if constexpr (array_like<value_t<T>>)
300 {
301 for (auto& element : value_of(output))
302 {
303 ret = lo_message_add(message, type, element);
304 if (ret < 0) break;
305 }
306 }
307 else ret = lo_message_add(message, type, value_of(output));
308
309 if (ret < 0)
310 {
311 lo_message_free(message);
312 return;
313 }
314 }
315
316 int ret = lo_bundle_add_message(bundle, osc_path_v<T, Components>, message);
317 if (ret < 0) fprintf(stderr, "liblo: unable to add message to bundle.\n");
318 //lo_message_free(message); // bundle makes its own ref to message on success, so we need to free ours regardless
319 return;
320 });
321 int ret = lo_send_bundle(dst, bundle);
322 //if (ret < 0) fprintf( stderr, "liblo: error %d sending bundle --- %s\n"
323 // , lo_address_errno(dst)
324 // , lo_address_errstr(dst)
325 // );
326 lo_bundle_free_recursive(bundle);
327 }
328 }
329};
330
333} }
Definition sygac-endpoints.hpp:92
Document the author of an entity, e.g. a component or binding.
Definition sygah-metadata.hpp:39
Document the copyright statement of an entity, e.g. a component or binding.
Definition sygah-metadata.hpp:47
Document a textual description of an entity, e.g. an endpoint, component or binding.
Definition sygah-metadata.hpp:35
Document the copyright license of an entity, e.g. a component or binding.
Definition sygah-metadata.hpp:45
Document the name of an entity, e.g. an endpoint, component, or binding.
Definition sygah-metadata.hpp:33
Definition sygbp-liblo.hpp:36
Definition sygbp-liblo.hpp:51
Definition sygbp-liblo.hpp:35
Session data tag helper.
Definition sygah-endpoints.hpp:252
A text string endpoint with occasional message semantics.
Definition sygah-endpoints.hpp:320
A two-state integer endpoint with persistent value semantics.
Definition sygah-endpoints.hpp:289
Document a textual description of the version number of an entity, e.g. a component or binding.
Definition sygah-metadata.hpp:51