1 /*
2 * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
3 *
4 * This is free software: you can redistribute it and/or modify
5 * it under the terms of the Artistic License 2.0 as published by
6 * The Perl Foundation.
7 *
8 * This source is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * Artistic License 2.0 for more details.
12 *
13 * You should have received a copy of the Artistic License 2.0
14 * along the source as a COPYING file. If not, obtain it from
15 * http://www.perlfoundation.org/artistic_license_2_0.
16 */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <ctype.h>
22 #include <math.h>
23
24 #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
25 # include <pthread_np.h>
26 typedef cpuset_t cpu_set_t;
27 #endif
28
29 #include <synthpod_bin.h>
30
31 #define NANO_SECONDS 1000000000
32
33 typedef struct _prog_t prog_t;
34
35 struct _prog_t {
36 bin_t bin;
37
38 LV2_Atom_Forge forge;
39
40 save_state_t save_state;
41 atomic_int kill;
42 pthread_t thread;
43
44 uint32_t srate;
45 uint32_t frsize;
46 uint32_t nfrags;
47 uint32_t seq_size;
48
49 LV2_OSC_Schedule osc_sched;
50 struct timespec cur_ntp;
51 struct timespec nxt_ntp;
52 struct {
53 uint64_t cur_frames;
54 uint64_t ref_frames;
55 double dT;
56 double dTm1;
57 } cycle;
58 };
59
60 static inline void
_ntp_now(cross_clock_t * clk,struct timespec * ntp)61 _ntp_now(cross_clock_t *clk, struct timespec *ntp)
62 {
63 cross_clock_gettime(clk, ntp);
64 ntp->tv_sec += JAN_1970; // convert NTP to OSC time
65 }
66
67 static inline void
_ntp_clone(struct timespec * dst,struct timespec * src)68 _ntp_clone(struct timespec *dst, struct timespec *src)
69 {
70 dst->tv_sec = src->tv_sec;
71 dst->tv_nsec = src->tv_nsec;
72 }
73
74 static inline void
_ntp_add_nanos(struct timespec * ntp,uint64_t nanos)75 _ntp_add_nanos(struct timespec *ntp, uint64_t nanos)
76 {
77 ntp->tv_nsec += nanos;
78 while(ntp->tv_nsec >= NANO_SECONDS) // has overflowed
79 {
80 ntp->tv_sec += 1;
81 ntp->tv_nsec -= NANO_SECONDS;
82 }
83 }
84
85 static inline double
_ntp_diff(struct timespec * from,struct timespec * to)86 _ntp_diff(struct timespec *from, struct timespec *to)
87 {
88 double diff = to->tv_sec;
89 diff -= from->tv_sec;
90 diff += 1e-9 * to->tv_nsec;
91 diff -= 1e-9 * from->tv_nsec;
92
93 return diff;
94 }
95
96 __realtime static inline void
_process(prog_t * handle)97 _process(prog_t *handle)
98 {
99 bin_t *bin = &handle->bin;
100 sp_app_t *app = bin->app;
101
102 const uint32_t nsamples = handle->frsize;
103
104 const uint64_t nanos_per_period = (uint64_t)nsamples * NANO_SECONDS / handle->srate;
105 handle->cycle.cur_frames = 0; // initialize frame counter
106 _ntp_now(&bin->clk_real, &handle->nxt_ntp);
107
108 const unsigned n_period = handle->nfrags;
109
110 struct timespec sleep_to;
111 cross_clock_gettime(&bin->clk_mono, &sleep_to);
112
113 while(!atomic_load_explicit(&handle->kill, memory_order_relaxed))
114 {
115 cross_clock_nanosleep(&bin->clk_mono, true, &sleep_to);
116 _ntp_add_nanos(&sleep_to, nanos_per_period * n_period);
117
118 uint32_t na = nsamples * n_period;
119
120 // current time is next time from last cycle
121 _ntp_clone(&handle->cur_ntp, &handle->nxt_ntp);
122
123 // extrapolate new nxt_ntp
124 struct timespec nxt_ntp;
125 _ntp_now(&bin->clk_real, &nxt_ntp);
126 _ntp_clone(&handle->nxt_ntp, &nxt_ntp);
127
128 // reset ref_frames
129 handle->cycle.ref_frames = handle->cycle.cur_frames;
130
131 // calculate apparent period
132 _ntp_add_nanos(&nxt_ntp, nanos_per_period);
133 double diff = _ntp_diff(&handle->cur_ntp, &nxt_ntp);
134
135 // calculate apparent samples per period
136 handle->cycle.dT = nsamples / diff;
137 handle->cycle.dTm1 = 1.0 / handle->cycle.dT;
138
139 for( ; na >= nsamples;
140 na -= nsamples,
141 handle->cycle.ref_frames += nsamples,
142 _ntp_add_nanos(&handle->nxt_ntp, nanos_per_period) )
143 {
144 const sp_app_system_source_t *sources = sp_app_get_system_sources(app);
145
146 if(sp_app_bypassed(app))
147 {
148 //const sp_app_system_sink_t *sinks = sp_app_get_system_sinks(app);
149
150 //fprintf(stderr, "app is bypassed\n");
151
152 bin_process_pre(bin, nsamples, true);
153 bin_process_post(bin);
154
155 continue;
156 }
157
158 // fill input buffers
159 for(const sp_app_system_source_t *source=sources;
160 source->type != SYSTEM_PORT_NONE;
161 source++)
162 {
163 switch(source->type)
164 {
165 case SYSTEM_PORT_NONE:
166 case SYSTEM_PORT_AUDIO:
167 case SYSTEM_PORT_CONTROL:
168 case SYSTEM_PORT_CV:
169 case SYSTEM_PORT_OSC:
170 case SYSTEM_PORT_MIDI:
171 break;
172
173 case SYSTEM_PORT_COM:
174 {
175 void *seq_in = source->buf;
176
177 LV2_Atom_Forge *forge = &handle->forge;
178 LV2_Atom_Forge_Frame frame;
179 lv2_atom_forge_set_buffer(forge, seq_in, SEQ_SIZE);
180 LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
181
182 const LV2_Atom_Object *obj;
183 size_t size;
184 while((obj = varchunk_read_request(bin->app_from_com, &size)))
185 {
186 if(ref)
187 ref = lv2_atom_forge_frame_time(forge, 0);
188 if(ref)
189 ref = lv2_atom_forge_raw(forge, obj, size);
190 if(ref)
191 lv2_atom_forge_pad(forge, size);
192
193 varchunk_read_advance(bin->app_from_com);
194 }
195 if(ref)
196 lv2_atom_forge_pop(forge, &frame);
197 else
198 lv2_atom_sequence_clear(seq_in);
199
200 break;
201 }
202 }
203 }
204
205 bin_process_pre(bin, nsamples, false);
206
207 const sp_app_system_sink_t *sinks = sp_app_get_system_sinks(app);
208
209 // fill output buffers
210 for(const sp_app_system_sink_t *sink=sinks;
211 sink->type != SYSTEM_PORT_NONE;
212 sink++)
213 {
214 switch(sink->type)
215 {
216 case SYSTEM_PORT_NONE:
217 case SYSTEM_PORT_CONTROL:
218 case SYSTEM_PORT_CV:
219 case SYSTEM_PORT_OSC:
220 case SYSTEM_PORT_AUDIO:
221 case SYSTEM_PORT_MIDI:
222 break;
223 case SYSTEM_PORT_COM:
224 {
225 const LV2_Atom_Sequence *seq_out = sink->buf;
226
227 LV2_ATOM_SEQUENCE_FOREACH(seq_out, ev)
228 {
229 const LV2_Atom *atom = &ev->body;
230
231 // try do process events directly
232 bin->advance_ui = sp_app_from_ui(bin->app, atom);
233 if(!bin->advance_ui) // queue event in ringbuffer instead
234 {
235 //fprintf(stderr, "plugin ui direct is blocked\n");
236
237 void *ptr;
238 size_t size = lv2_atom_total_size(atom);
239 if((ptr = varchunk_write_request(bin->app_from_app, size)))
240 {
241 memcpy(ptr, atom, size);
242 varchunk_write_advance(bin->app_from_app, size);
243 }
244 else
245 {
246 bin_log_trace(bin, "%s: app_from_app ringbuffer full\n", __func__);
247 //FIXME
248 }
249 }
250 }
251 break;
252 }
253 }
254 }
255
256 bin_process_post(bin);
257 }
258
259 // increase cur_frames
260 handle->cycle.cur_frames = handle->cycle.ref_frames;
261 //sched_yield();
262 }
263 }
264
265 __non_realtime static void *
_rt_thread(void * data)266 _rt_thread(void *data)
267 {
268 prog_t *handle = data;
269 bin_t *bin = &handle->bin;
270
271 bin->dsp_thread = pthread_self();
272
273 if(handle->bin.audio_prio)
274 {
275 struct sched_param schedp;
276 memset(&schedp, 0, sizeof(struct sched_param));
277 schedp.sched_priority = handle->bin.audio_prio;
278
279 if(schedp.sched_priority)
280 {
281 if(pthread_setschedparam(bin->dsp_thread, SCHED_FIFO, &schedp))
282 bin_log_error(bin, "%s: pthread_setschedparam error\n", __func__);
283 }
284 }
285
286 if(handle->bin.cpu_affinity)
287 {
288 cpu_set_t cpuset;
289 CPU_ZERO(&cpuset);
290 CPU_SET(0, &cpuset);
291 if(pthread_setaffinity_np(bin->dsp_thread, sizeof(cpu_set_t), &cpuset))
292 bin_log_error(bin, "%s: pthread_setaffinity_np error\n", __func__);
293 }
294
295 _process(handle);
296
297 return NULL;
298 }
299
300 __non_realtime static void *
_system_port_add(void * data,system_port_t type,const char * short_name,const char * pretty_name,const char * designation,bool input,uint32_t order)301 _system_port_add(void *data, system_port_t type, const char *short_name,
302 const char *pretty_name, const char *designation, bool input, uint32_t order)
303 {
304 bin_t *bin = data;
305 prog_t *handle = (void *)bin - offsetof(prog_t, bin);
306 (void)handle;
307
308 switch(type)
309 {
310 case SYSTEM_PORT_NONE:
311 case SYSTEM_PORT_CONTROL:
312 case SYSTEM_PORT_AUDIO:
313 case SYSTEM_PORT_CV:
314 case SYSTEM_PORT_MIDI:
315 case SYSTEM_PORT_OSC:
316 case SYSTEM_PORT_COM:
317 // unsupported, skip
318 break;
319 }
320
321 return NULL;
322 }
323
324 __non_realtime static void
_system_port_del(void * data,void * sys_port)325 _system_port_del(void *data, void *sys_port)
326 {
327 bin_t *bin = data;
328 prog_t *handle = (void *)bin - offsetof(prog_t, bin);
329 (void)handle;
330
331 // unsupported, skip
332 }
333
334 __non_realtime static void
_saved(bin_t * bin,int status)335 _saved(bin_t *bin, int status)
336 {
337 prog_t *handle = (void *)bin - offsetof(prog_t, bin);
338
339 if(handle->save_state == SAVE_STATE_NSM)
340 {
341 nsmc_saved(bin->nsm, status);
342 }
343 handle->save_state = SAVE_STATE_INTERNAL;
344
345 if(atomic_load_explicit(&handle->kill, memory_order_relaxed))
346 {
347 bin_quit(bin);
348 }
349 }
350
351 __non_realtime static int
_open(const char * path,const char * name,const char * id,void * data)352 _open(const char *path, const char *name, const char *id, void *data)
353 {
354 bin_t *bin = data;
355 prog_t *handle = (void *)bin - offsetof(prog_t, bin);
356 (void)name;
357
358 if(bin->path)
359 free(bin->path);
360 bin->path = strdup(path);
361
362 // synthpod init
363 bin->app_driver.sample_rate = handle->srate;
364 bin->app_driver.update_rate = handle->bin.update_rate;
365 bin->app_driver.max_block_size = handle->frsize;
366 bin->app_driver.min_block_size = 1;
367 bin->app_driver.seq_size = handle->seq_size;
368 bin->app_driver.num_periods = handle->nfrags;
369
370 // app init
371 bin->app = sp_app_new(NULL, &bin->app_driver, bin);
372
373 // alsa activate
374 atomic_init(&handle->kill, 0);
375 if(pthread_create(&handle->thread, NULL, _rt_thread, handle))
376 bin_log_error(bin, "%s: creation of realtime thread failed\n", __func__);
377
378 bin_bundle_load(bin, bin->path);
379 nsmc_opened(bin->nsm, 0);
380
381 return 0; // success
382 }
383
384 __non_realtime static int
_save(void * data)385 _save(void *data)
386 {
387 bin_t *bin = data;
388 prog_t *handle = (void *)bin - offsetof(prog_t, bin);
389
390 handle->save_state = SAVE_STATE_NSM;
391 bin_bundle_save(bin, bin->path);
392 _saved(bin, 0);
393
394 return 0; // success
395 }
396
397 __non_realtime static int
_show(void * data)398 _show(void *data)
399 {
400 bin_t *bin = data;
401
402 return bin_show(bin);
403 }
404
405 __non_realtime static int
_hide(void * data)406 _hide(void *data)
407 {
408 bin_t *bin = data;
409
410 return bin_hide(bin);
411 }
412
413 static const nsmc_driver_t nsm_driver = {
414 .open = _open,
415 .save = _save,
416 .show = _show,
417 .hide = _hide
418 };
419
420 // rt
421 __realtime static double
_osc_schedule_osc2frames(LV2_OSC_Schedule_Handle instance,uint64_t timestamp)422 _osc_schedule_osc2frames(LV2_OSC_Schedule_Handle instance, uint64_t timestamp)
423 {
424 prog_t *handle = instance;
425
426 if(timestamp == 1ULL)
427 return 0; // inject at start of period
428
429 const uint64_t time_sec = timestamp >> 32;
430 const uint64_t time_frac = timestamp & 0xffffffff;
431
432 const double diff = (time_sec - handle->cur_ntp.tv_sec)
433 + time_frac * 0x1p-32
434 - handle->cur_ntp.tv_nsec * 1e-9;
435
436 const double frames = diff * handle->cycle.dT
437 - handle->cycle.ref_frames
438 + handle->cycle.cur_frames;
439
440 return frames;
441 }
442
443 // rt
444 __realtime static uint64_t
_osc_schedule_frames2osc(LV2_OSC_Schedule_Handle instance,double frames)445 _osc_schedule_frames2osc(LV2_OSC_Schedule_Handle instance, double frames)
446 {
447 prog_t *handle = instance;
448
449 double diff = (frames - handle->cycle.cur_frames + handle->cycle.ref_frames)
450 * handle->cycle.dTm1;
451 diff += handle->cur_ntp.tv_nsec * 1e-9;
452 diff += handle->cur_ntp.tv_sec;
453
454 double time_sec_d;
455 double time_frac_d = modf(diff, &time_sec_d);
456
457 uint64_t time_sec = time_sec_d;
458 uint64_t time_frac = time_frac_d * 0x1p32;
459 if(time_frac >= 0x100000000ULL) // illegal overflow
460 time_frac = 0xffffffffULL;
461
462 uint64_t timestamp = (time_sec << 32) | time_frac;
463
464 return timestamp;
465 }
466
467 int
main(int argc,char ** argv)468 main(int argc, char **argv)
469 {
470 mlockall(MCL_CURRENT | MCL_FUTURE);
471
472 static prog_t handle;
473 bin_t *bin = &handle.bin;
474
475 handle.srate = 48000;
476 handle.frsize = 1024;
477 handle.nfrags = 3; //TODO make this configurable
478 handle.seq_size = SEQ_SIZE;
479
480 bin->audio_prio = 70;
481 bin->worker_prio = 60;
482 bin->num_slaves = sysconf(_SC_NPROCESSORS_ONLN) - 1;
483 bin->bad_plugins = false;
484 bin->has_gui = false;
485 bin->kill_gui = false;
486 snprintf(bin->socket_path, sizeof(bin->socket_path), "shm:///synthpod-%i", getpid());
487 bin->update_rate = 25;
488 bin->cpu_affinity = false;
489
490 fprintf(stderr,
491 "Synthpod "SYNTHPOD_VERSION"\n"
492 "Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)\n"
493 "Released under Artistic License 2.0 by Open Music Kontrollers\n");
494
495 int c;
496 while((c = getopt(argc, argv, "vhgGkKbBaAy:Yw:Wl:r:p:s:c:f:")) != -1)
497 {
498 switch(c)
499 {
500 case 'v':
501 fprintf(stderr,
502 "--------------------------------------------------------------------\n"
503 "This is free software: you can redistribute it and/or modify\n"
504 "it under the terms of the Artistic License 2.0 as published by\n"
505 "The Perl Foundation.\n"
506 "\n"
507 "This source is distributed in the hope that it will be useful,\n"
508 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
509 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
510 "Artistic License 2.0 for more details.\n"
511 "\n"
512 "You should have received a copy of the Artistic License 2.0\n"
513 "along the source as a COPYING file. If not, obtain it from\n"
514 "http://www.perlfoundation.org/artistic_license_2_0.\n\n");
515 return 0;
516 case 'h':
517 fprintf(stderr,
518 "--------------------------------------------------------------------\n"
519 "USAGE\n"
520 " %s [OPTIONS] [BUNDLE_PATH]\n"
521 "\n"
522 "OPTIONS\n"
523 " [-v] print version and full license information\n"
524 " [-h] print usage information\n"
525 " [-g] load GUI\n"
526 " [-G] do NOT load GUI (default)\n"
527 " [-k] kill DSP with GUI\n"
528 " [-K] do NOT kill DSP with GUI (default)\n"
529 " [-b] enable bad plugins\n"
530 " [-B] disable bad plugins (default)\n"
531 " [-a] enable CPU affinity\n"
532 " [-A] disable CPU affinity (default)\n"
533 " [-y] audio-priority audio thread realtime priority (70)\n"
534 " [-Y] do NOT use audio thread realtime priority\n"
535 " [-w] worker-priority worker thread realtime priority (60)\n"
536 " [-W] do NOT use worker thread realtime priority\n"
537 " [-l] link-path socket link path (shm:///synthpod)\n"
538 " [-r] sample-rate sample rate (48000)\n"
539 " [-p] sample-period frames per period (1024)\n"
540 " [-s] sequence-size minimum sequence size (8192)\n"
541 " [-c] slave-cores number of slave cores (auto)\n"
542 " [-f] update-rate GUI update rate (25)\n\n"
543 , argv[0]);
544 return 0;
545 case 'g':
546 bin->has_gui = true;
547 break;
548 case 'G':
549 bin->has_gui = false;
550 break;
551 case 'k':
552 bin->kill_gui = true;
553 break;
554 case 'K':
555 bin->kill_gui = false;
556 break;
557 case 'b':
558 bin->bad_plugins = true;
559 break;
560 case 'B':
561 bin->bad_plugins = false;
562 break;
563 case 'a':
564 bin->cpu_affinity = true;
565 break;
566 case 'A':
567 bin->cpu_affinity = false;
568 break;
569 case 'y':
570 bin->audio_prio = atoi(optarg);
571 break;
572 case 'Y':
573 bin->audio_prio = 0;
574 break;
575 case 'w':
576 bin->worker_prio = atoi(optarg);
577 break;
578 case 'W':
579 bin->worker_prio = 0;
580 break;
581 case 'l':
582 snprintf(bin->socket_path, sizeof(bin->socket_path), "%s", optarg);
583 break;
584 case 'r':
585 handle.srate = atoi(optarg);
586 break;
587 case 'p':
588 handle.frsize = atoi(optarg);
589 break;
590 case 's':
591 handle.seq_size = MAX(SEQ_SIZE, atoi(optarg));
592 break;
593 case 'c':
594 if(atoi(optarg) < bin->num_slaves)
595 bin->num_slaves = atoi(optarg);
596 break;
597 case 'f':
598 bin->update_rate = atoi(optarg);
599 break;
600 case '?':
601 if( (optopt == 'r') || (optopt == 'p') || (optopt == 's') || (optopt == 'c')
602 || (optopt == 'l') || (optopt == 'f') )
603 fprintf(stderr, "Option `-%c' requires an argument.\n", optopt);
604 else if(isprint(optopt))
605 fprintf(stderr, "Unknown option `-%c'.\n", optopt);
606 else
607 fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt);
608 return -1;
609 default:
610 return -1;
611 }
612 }
613
614 bin_init(bin, handle.srate);
615
616 LV2_URID_Map *map = bin->map;
617
618 lv2_atom_forge_init(&handle.forge, map);
619
620 bin->app_driver.system_port_add = _system_port_add;
621 bin->app_driver.system_port_del = _system_port_del;
622
623 handle.osc_sched.osc2frames = _osc_schedule_osc2frames;
624 handle.osc_sched.frames2osc = _osc_schedule_frames2osc;
625 handle.osc_sched.handle = &handle;
626 bin->app_driver.osc_sched = &handle.osc_sched;
627 bin->app_driver.features = SP_APP_FEATURE_FIXED_BLOCK_LENGTH; // always true for DUMMY
628 if(handle.frsize && !(handle.frsize & (handle.frsize - 1))) // check for powerOf2
629 bin->app_driver.features |= SP_APP_FEATURE_POWER_OF_2_BLOCK_LENGTH;
630
631 // run
632 bin_run(bin, argv, &nsm_driver, NULL, NULL);
633
634 // stop
635 bin_stop(bin);
636
637 // stop rt thread
638 if(handle.thread)
639 {
640 atomic_store_explicit(&handle.kill, 1, memory_order_relaxed);
641 pthread_join(handle.thread, NULL);
642 }
643
644 // deinit
645 bin_deinit(bin);
646
647 munlockall();
648
649 return 0;
650 }
651