1 /*
2 * oscsend - Send OpenSound Control message.
3 *
4 * Copyright (C) 2016 Joseph Malloch <joseph.malloch@gmail.com>
5 * Copyright (C) 2008 Kentaro Fukuchi <kentaro@fukuchi.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <math.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <config.h>
30 #include <limits.h>
31 #include <unistd.h>
32 #include <lo/lo.h>
33
34 static FILE* input_file = 0;
35 static double multiplier = 1.0/((double)(1LL<<32));
36
usage(void)37 void usage(void)
38 {
39 printf("oscsendfile version %s\n"
40 "Copyright (C) 2016 Joseph Malloch\n"
41 " adapted from oscsend.c (C) 2008 Kentaro Fukuchi\n\n"
42 "Usage: oscsend hostname port file <speed>\n"
43 "or oscsend url file <speed>\n"
44 "Send OpenSound Control messages from a file via UDP.\n\n"
45 "Description\n"
46 "hostname: specifies the remote host's name.\n"
47 "port : specifies the port number to connect to the remote host.\n"
48 "url : specifies the destination parameters using a liblo URL.\n"
49 " e.g. UDP \"osc.udp://localhost:9000\"\n"
50 " Multicast \"osc.udp://224.0.1.9:9000\"\n"
51 " TCP \"osc.tcp://localhost:9000\"\n"
52 "speed : specifies a speed multiplier.\n",
53 VERSION);
54 printf("Example\n"
55 "$ oscsendfile localhost 7777 myfile.txt 2.5\n");
56 }
57
create_message(char ** argv)58 lo_message create_message(char **argv)
59 {
60 /* Note:
61 * argv[0] <- types
62 * argv[1..] <- values
63 */
64 int i, argi;
65 lo_message message;
66 const char *types;
67 char *arg;
68 int values;
69
70 message = lo_message_new();
71 if (argv[0] == NULL) {
72 /* empty message is allowed. */
73 values = 0;
74 } else {
75 types = argv[0];
76 values = strlen(types);
77 }
78
79 argi = 1;
80 for (i = 0; i < values; i++) {
81 switch(types[i]) {
82 case LO_INT32:
83 case LO_FLOAT:
84 case LO_STRING:
85 case LO_BLOB:
86 case LO_INT64:
87 case LO_TIMETAG:
88 case LO_DOUBLE:
89 case LO_SYMBOL:
90 case LO_CHAR:
91 case LO_MIDI:
92 arg = argv[argi];
93 if (arg == NULL) {
94 fprintf(stderr, "Value #%d is not given.\n", i + 1);
95 goto EXIT;
96 }
97 break;
98 default:
99 break;
100 }
101 switch (types[i]) {
102 case LO_INT32:
103 {
104 char *endp;
105 int64_t v;
106
107 v = strtol(arg, &endp, 10);
108 if (*endp != '\0') {
109 fprintf(stderr, "An invalid value was given: '%s'\n",
110 arg);
111 goto EXIT;
112 }
113 if ((v == LONG_MAX || v == LONG_MIN) && errno == ERANGE) {
114 fprintf(stderr, "Value out of range: '%s'\n", arg);
115 goto EXIT;
116 }
117 if (v > INT_MAX || v < INT_MIN) {
118 fprintf(stderr, "Value out of range: '%s'\n", arg);
119 goto EXIT;
120 }
121 lo_message_add_int32(message, (int32_t) v);
122 argi++;
123 break;
124 }
125 case LO_INT64:
126 {
127 char *endp;
128 int64_t v;
129
130 v = strtoll(arg, &endp, 10);
131 if (*endp != '\0') {
132 fprintf(stderr, "An invalid value was given: '%s'\n",
133 arg);
134 goto EXIT;
135 }
136 if ((v == LONG_MAX || v == LONG_MIN) && errno == ERANGE) {
137 fprintf(stderr, "Value out of range: '%s'\n", arg);
138 goto EXIT;
139 }
140 lo_message_add_int64(message, v);
141 argi++;
142 break;
143 }
144 case LO_FLOAT:
145 {
146 char *endp;
147 float v;
148
149 #ifdef __USE_ISOC99
150 v = strtof(arg, &endp);
151 #else
152 v = (float) strtod(arg, &endp);
153 #endif /* __USE_ISOC99 */
154 if (*endp != '\0') {
155 fprintf(stderr, "An invalid value was given: '%s'\n",
156 arg);
157 goto EXIT;
158 }
159 lo_message_add_float(message, v);
160 argi++;
161 break;
162 }
163 case LO_DOUBLE:
164 {
165 char *endp;
166 double v;
167
168 v = strtod(arg, &endp);
169 if (*endp != '\0') {
170 perror(NULL);
171 fprintf(stderr, "An invalid value was given: '%s'\n",
172 arg);
173 goto EXIT;
174 }
175 lo_message_add_double(message, v);
176 argi++;
177 break;
178 }
179 case LO_STRING:
180 case LO_SYMBOL:
181 if (arg[strlen(arg)-1] == '"')
182 arg[strlen(arg)-1] = '\0';
183 if (arg[0] == '"')
184 lo_message_add_string(message, arg+1);
185 else
186 lo_message_add_string(message, arg);
187 argi++;
188 break;
189 case LO_CHAR:
190 lo_message_add_char(message, arg[0]);
191 argi++;
192 break;
193 case LO_MIDI:
194 {
195 unsigned int midi;
196 uint8_t packet[4];
197 int ret;
198
199 ret = sscanf(arg, "%08x", &midi);
200 if (ret != 1) {
201 fprintf(stderr,
202 "An invalid hexadecimal value was given: '%s'\n",
203 arg);
204 goto EXIT;
205 }
206 packet[0] = (midi >> 24) & 0xff;
207 packet[1] = (midi >> 16) & 0xff;
208 packet[2] = (midi >> 8) & 0xff;
209 packet[3] = midi & 0xff;
210 lo_message_add_midi(message, packet);
211 argi++;
212 break;
213 }
214 case LO_TRUE:
215 lo_message_add_true(message);
216 break;
217 case LO_FALSE:
218 lo_message_add_false(message);
219 break;
220 case LO_NIL:
221 lo_message_add_nil(message);
222 break;
223 case LO_INFINITUM:
224 lo_message_add_infinitum(message);
225 break;
226 default:
227 fprintf(stderr, "Type '%c' is not supported or invalid.\n",
228 types[i]);
229 goto EXIT;
230 break;
231 }
232 }
233
234 return message;
235 EXIT:
236 lo_message_free(message);
237 return NULL;
238 }
239
timetag_add(lo_timetag * tt,lo_timetag addend)240 void timetag_add(lo_timetag *tt, lo_timetag addend)
241 {
242 tt->sec += addend.sec;
243 tt->frac += addend.frac;
244 if (tt->frac < addend.frac) // overflow
245 tt->sec++;
246 }
247
timetag_subtract(lo_timetag * tt,lo_timetag subtrahend)248 void timetag_subtract(lo_timetag *tt, lo_timetag subtrahend)
249 {
250 tt->sec -= subtrahend.sec;
251 if (tt->frac < subtrahend.frac) // overflow
252 --tt->sec;
253 tt->frac -= subtrahend.frac;
254 }
255
timetag_diff(const lo_timetag a,const lo_timetag b)256 double timetag_diff(const lo_timetag a, const lo_timetag b)
257 {
258 return ((double)a.sec - (double)b.sec +
259 ((double)a.frac - (double)b.frac) * multiplier);
260 }
261
timetag_double(const lo_timetag tt)262 double timetag_double(const lo_timetag tt)
263 {
264 return (double)tt.sec + (double)tt.frac * multiplier;
265 }
266
timetag_multiply(lo_timetag * tt,double d)267 void timetag_multiply(lo_timetag *tt, double d)
268 {
269 d *= timetag_double(*tt);
270 tt->sec = floor(d);
271 d -= tt->sec;
272 tt->frac = (uint32_t) (d * (double)(1LL<<32));
273 }
274
send_file(lo_address target,double speed)275 int send_file(lo_address target, double speed) {
276 double speedmul = 1.0 / speed;
277 const char *delim = " \r\n";
278 char *args[64];
279 char str[1024], *p, *s, *path;
280 lo_timetag tt_start = {0, 0}, tt_start_file, tt_now;
281 lo_timetag tt1 = {0, 0}, tt2 = {0, 0}, *tt_last = &tt1, *tt_this = &tt2;
282 int tt_init = 0, ret;
283 lo_message m;
284 lo_bundle b = 0;
285
286 lo_timetag_now(&tt_start);
287
288 while (fgets(str, 1024, input_file)) {
289 path = 0;
290 s = strtok_r(str, delim, &p);
291 m = lo_message_new();
292 if (!m)
293 return 1;
294
295 if (s) {
296 // check if first arg is timetag
297 if (s[0] != '/') {
298 // first argument is timetag
299 char *ttstr = strtok(s, ".");
300 if (ttstr) {
301 tt_this->sec = strtoul(ttstr, NULL, 16);
302 if (!tt_init)
303 tt_start_file.sec = tt_this->sec;
304 }
305 ttstr = strtok(0, ".");
306 if (ttstr) {
307 tt_this->frac = strtoul(ttstr, NULL, 16);
308 if (!tt_init)
309 tt_start_file.frac = tt_this->frac;
310 }
311 if (1) {
312 timetag_subtract(tt_this, tt_start_file);
313 timetag_multiply(tt_this, speedmul);
314 timetag_add(tt_this, tt_start);
315 }
316 else {
317 if (!tt_init)
318 timetag_subtract(&tt_start, tt_start_file);
319 timetag_add(tt_this, tt_start);
320 }
321 tt_init = 1;
322 s = strtok_r(0, delim, &p);
323 }
324 else {
325 tt_this->sec = 0;
326 tt_this->frac = 1;
327 }
328 }
329 if (s)
330 path = s;
331 else
332 continue;
333
334 s = strtok_r(0, delim, &p);
335 if (s)
336 args[0] = s; // types
337
338 int i = 0;
339 while ((s = strtok_r(0, delim, &p))) {
340 args[++i] = s;
341 }
342
343 m = create_message(args);
344 if (!m) {
345 fprintf(stderr, "Failed to create OSC message.\n");
346 return 1;
347 }
348
349 if (b && memcmp(tt_this, tt_last, sizeof(lo_timetag))==0) {
350 lo_bundle_add_message(b, path, m);
351 }
352 else {
353 // wait for timestamp?
354 lo_timetag_now(&tt_now);
355 double wait_time = timetag_diff(*tt_last, tt_now);
356 if (wait_time > 0.) {
357 usleep(wait_time * 1000000);
358 }
359 if (b) {
360 ret = lo_send_bundle(target, b);
361 }
362 b = lo_bundle_new(*tt_this);
363 lo_bundle_add_message(b, path, m);
364
365 lo_timetag *tmp = tt_last;
366 tt_last = tt_this;
367 tt_this = tmp;
368 }
369
370 if (ret == -1)
371 return ret;
372 }
373
374 if (b) {
375 // wait for timestamp?
376 lo_timetag_now(&tt_now);
377 double wait_time = timetag_diff(*tt_last, tt_now);
378 if (wait_time > 0.) {
379 usleep(wait_time * 1000000);
380 }
381 lo_send_bundle(target, b);
382 }
383
384 return 0;
385 }
386
main(int argc,char ** argv)387 int main(int argc, char **argv)
388 {
389 lo_address target;
390 int ret, i=1;
391
392 if (argc < 2) {
393 usage();
394 exit(1);
395 }
396
397 if (argv[i] == NULL) {
398 fprintf(stderr, "No hostname is given.\n");
399 exit(1);
400 }
401
402 if (strstr(argv[i], "://")!=0) {
403 target = lo_address_new_from_url(argv[i]);
404 if (target == NULL) {
405 fprintf(stderr, "Failed to open %s\n", argv[i]);
406 exit(1);
407 }
408 i++;
409 }
410 else if (argv[i+1] == NULL) {
411 fprintf(stderr, "No port number is given.\n");
412 exit(1);
413 }
414 else {
415 target = lo_address_new(argv[i], argv[i+1]);
416 if (target == NULL) {
417 fprintf(stderr, "Failed to open %s:%s\n", argv[i], argv[i+1]);
418 exit(1);
419 }
420 lo_address_set_ttl(target, 1);
421 i += 2;
422 }
423
424 if (argv[i] == NULL) {
425 fprintf(stderr, "No %s given.\n", argc == i+1 ? "filename" : "path");
426 exit(1);
427 }
428
429 // next arg should be filename
430 input_file = fopen(argv[i], "r");
431 if (!input_file) {
432 fprintf(stderr, "Failed to open file `%s' for reading.\n", argv[i]);
433 exit(1);
434 }
435
436 double speed = 1.0;
437 if (argc > i+1) {
438 // optional speed argument
439 speed = atof(argv[i+1]);
440 }
441 ret = send_file(target, speed);
442
443 if (ret == -1) {
444 fprintf(stderr, "An error occurred: %s\n",
445 lo_address_errstr(target));
446 exit(1);
447 }
448
449 return 0;
450 }
451