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