A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/* Copyright (c) 1997-1999 Miller Puckette.
2* For information on usage and redistribution, and for a DISCLAIMER OF ALL
3* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */
4
5#ifdef ROCKBOX
6#include "plugin.h"
7#include "../../pdbox.h"
8#endif
9
10/* scheduling stuff */
11
12#include "m_pd.h"
13#include "m_imp.h"
14#include "s_stuff.h"
15
16 /* LATER consider making this variable. It's now the LCM of all sample
17 rates we expect to see: 32000, 44100, 48000, 88200, 96000. */
18#define TIMEUNITPERSEC (32.*441000.)
19
20
21/* T.Grill - enable PD global thread locking - sys_lock, sys_unlock, sys_trylock functions */
22#ifndef ROCKBOX
23#define THREAD_LOCKING
24#include "pthread.h"
25#endif
26
27
28static int sys_quit;
29#ifndef ROCKBOX
30static
31#endif
32t_time sys_time;
33#ifndef ROCKBOX
34static
35#endif
36t_time sys_time_per_msec = TIMEUNITPERSEC / 1000.;
37
38int sys_schedblocksize = DEFDACBLKSIZE;
39int sys_usecsincelastsleep(void);
40int sys_sleepgrain;
41
42typedef void (*t_clockmethod)(void *client);
43
44struct _clock
45{
46 t_time c_settime;
47 void *c_owner;
48 t_clockmethod c_fn;
49 struct _clock *c_next;
50};
51
52t_clock *clock_setlist;
53
54#ifdef UNIX
55#include <unistd.h>
56#endif
57
58t_clock *clock_new(void *owner, t_method fn)
59{
60 t_clock *x = (t_clock *)getbytes(sizeof *x);
61 x->c_settime = -1;
62 x->c_owner = owner;
63 x->c_fn = (t_clockmethod)fn;
64 x->c_next = 0;
65 return (x);
66}
67
68void clock_unset(t_clock *x)
69{
70 if (x->c_settime >= 0)
71 {
72 if (x == clock_setlist) clock_setlist = x->c_next;
73 else
74 {
75 t_clock *x2 = clock_setlist;
76 while (x2->c_next != x) x2 = x2->c_next;
77 x2->c_next = x->c_next;
78 }
79 x->c_settime = -1;
80 }
81}
82
83 /* set the clock to call back at an absolute system time */
84void clock_set(t_clock *x, t_time setticks)
85{
86 if (setticks < sys_time) setticks = sys_time;
87 clock_unset(x);
88 x->c_settime = setticks;
89 if (clock_setlist && clock_setlist->c_settime <= setticks)
90 {
91 t_clock *cbefore, *cafter;
92 for (cbefore = clock_setlist, cafter = clock_setlist->c_next;
93 cbefore; cbefore = cafter, cafter = cbefore->c_next)
94 {
95 if (!cafter || cafter->c_settime > setticks)
96 {
97 cbefore->c_next = x;
98 x->c_next = cafter;
99 return;
100 }
101 }
102 }
103 else x->c_next = clock_setlist, clock_setlist = x;
104}
105
106 /* set the clock to call back after a delay in msec */
107void clock_delay(t_clock *x, t_time delaytime)
108{
109 clock_set(x, sys_time + sys_time_per_msec * delaytime);
110}
111
112 /* get current logical time. We don't specify what units this is in;
113 use clock_gettimesince() to measure intervals from time of this call.
114 This was previously, incorrectly named "clock_getsystime"; the old
115 name is aliased to the new one in m_pd.h. */
116t_time clock_getlogicaltime( void)
117{
118 return (sys_time);
119}
120 /* OBSOLETE NAME */
121t_time clock_getsystime( void) { return (sys_time); }
122
123 /* elapsed time in milliseconds since the given system time */
124t_time clock_gettimesince(t_time prevsystime)
125{
126 return ((sys_time - prevsystime)/sys_time_per_msec);
127}
128
129 /* what value the system clock will have after a delay */
130t_time clock_getsystimeafter(t_time delaytime)
131{
132 return (sys_time + sys_time_per_msec * delaytime);
133}
134
135void clock_free(t_clock *x)
136{
137 clock_unset(x);
138 freebytes(x, sizeof *x);
139}
140
141
142/* the following routines maintain a real-execution-time histogram of the
143various phases of real-time execution. */
144
145static int sys_bin[] = {0, 2, 5, 10, 20, 30, 50, 100, 1000};
146#define NBIN (sizeof(sys_bin)/sizeof(*sys_bin))
147#define NHIST 10
148static int sys_histogram[NHIST][NBIN];
149#ifndef ROCKBOX
150static t_time sys_histtime;
151#endif
152static int sched_diddsp, sched_didpoll, sched_didnothing;
153
154#ifndef ROCKBOX
155static void sys_clearhist( void)
156{
157 unsigned int i, j;
158 for (i = 0; i < NHIST; i++)
159 for (j = 0; j < NBIN; j++) sys_histogram[i][j] = 0;
160 sys_histtime = sys_getrealtime();
161 sched_diddsp = sched_didpoll = sched_didnothing = 0;
162}
163#endif
164
165void sys_printhist( void)
166{
167 unsigned int i, j;
168 for (i = 0; i < NHIST; i++)
169 {
170 int doit = 0;
171 for (j = 0; j < NBIN; j++) if (sys_histogram[i][j]) doit = 1;
172 if (doit)
173 {
174 post("%2d %8d %8d %8d %8d %8d %8d %8d %8d", i,
175 sys_histogram[i][0],
176 sys_histogram[i][1],
177 sys_histogram[i][2],
178 sys_histogram[i][3],
179 sys_histogram[i][4],
180 sys_histogram[i][5],
181 sys_histogram[i][6],
182 sys_histogram[i][7]);
183 }
184 }
185 post("dsp %d, pollgui %d, nothing %d",
186 sched_diddsp, sched_didpoll, sched_didnothing);
187}
188
189#ifndef ROCKBOX
190static int sys_histphase;
191#endif
192
193int sys_addhist(int phase)
194{
195#ifdef ROCKBOX
196 (void) phase;
197#endif
198#ifndef FIXEDPOINT
199 int i, j, phasewas = sys_histphase;
200 t_time newtime = sys_getrealtime();
201 int msec = (newtime - sys_histtime) * 1000.;
202 for (j = NBIN-1; j >= 0; j--)
203 {
204 if (msec >= sys_bin[j])
205 {
206 sys_histogram[phasewas][j]++;
207 break;
208 }
209 }
210 sys_histtime = newtime;
211 sys_histphase = phase;
212 return (phasewas);
213#else
214 return 0;
215#endif
216}
217
218#define NRESYNC 20
219
220typedef struct _resync
221{
222 int r_ntick;
223 int r_error;
224} t_resync;
225
226static int oss_resyncphase = 0;
227static int oss_nresync = 0;
228static t_resync oss_resync[NRESYNC];
229
230
231static char *(oss_errornames[]) = {
232"unknown",
233"ADC blocked",
234"DAC blocked",
235"A/D/A sync",
236"data late"
237};
238
239void glob_audiostatus(void)
240{
241#ifdef ROCKBOX
242 int nresync, nresyncphase, i;
243#else
244 int dev, nresync, nresyncphase, i;
245#endif
246 nresync = (oss_nresync >= NRESYNC ? NRESYNC : oss_nresync);
247 nresyncphase = oss_resyncphase - 1;
248 post("audio I/O error history:");
249 post("seconds ago\terror type");
250 for (i = 0; i < nresync; i++)
251 {
252 int errtype;
253 if (nresyncphase < 0)
254 nresyncphase += NRESYNC;
255 errtype = oss_resync[nresyncphase].r_error;
256 if (errtype < 0 || errtype > 4)
257 errtype = 0;
258
259 post("%9.2f\t%s",
260 (sched_diddsp - oss_resync[nresyncphase].r_ntick)
261 * ((double)sys_schedblocksize) / sys_dacsr,
262 oss_errornames[errtype]);
263 nresyncphase--;
264 }
265}
266
267static int sched_diored;
268static int sched_dioredtime;
269static int sched_meterson;
270
271void sys_log_error(int type)
272{
273 oss_resync[oss_resyncphase].r_ntick = sched_diddsp;
274 oss_resync[oss_resyncphase].r_error = type;
275 oss_nresync++;
276 if (++oss_resyncphase == NRESYNC) oss_resyncphase = 0;
277 if (type != ERR_NOTHING && !sched_diored &&
278 (sched_diddsp >= sched_dioredtime))
279 {
280#ifndef ROCKBOX
281 sys_vgui("pdtk_pd_dio 1\n");
282#endif
283 sched_diored = 1;
284 }
285 sched_dioredtime =
286 sched_diddsp + (int)(sys_dacsr /(double)sys_schedblocksize);
287}
288
289static int sched_lastinclip, sched_lastoutclip,
290 sched_lastindb, sched_lastoutdb;
291
292void glob_ping(t_pd *dummy);
293
294#ifndef ROCKBOX
295static void sched_pollformeters( void)
296{
297 int inclip, outclip, indb, outdb;
298 static int sched_nextmeterpolltime, sched_nextpingtime;
299
300 /* if there's no GUI but we're running in "realtime", here is
301 where we arrange to ping the watchdog every 2 seconds. */
302#ifdef __linux__
303 if (sys_nogui && sys_hipriority && (sched_diddsp - sched_nextpingtime > 0))
304 {
305 glob_ping(0);
306 /* ping every 2 seconds */
307 sched_nextpingtime = sched_diddsp +
308 (2* sys_dacsr) /(int)sys_schedblocksize;
309 }
310#endif
311
312 if (sched_diddsp - sched_nextmeterpolltime < 0)
313 return;
314 if (sched_diored && (sched_diddsp - sched_dioredtime > 0))
315 {
316 sys_vgui("pdtk_pd_dio 0\n");
317 sched_diored = 0;
318 }
319 if (sched_meterson)
320 {
321 float inmax, outmax;
322 sys_getmeters(&inmax, &outmax);
323 indb = 0.5 + rmstodb(inmax);
324 outdb = 0.5 + rmstodb(outmax);
325 inclip = (inmax > 0.999);
326 outclip = (outmax >= 1.0);
327 }
328 else
329 {
330 indb = outdb = 0;
331 inclip = outclip = 0;
332 }
333 if (inclip != sched_lastinclip || outclip != sched_lastoutclip
334 || indb != sched_lastindb || outdb != sched_lastoutdb)
335 {
336 sys_vgui("pdtk_pd_meters %d %d %d %d\n", indb, outdb, inclip, outclip);
337 sched_lastinclip = inclip;
338 sched_lastoutclip = outclip;
339 sched_lastindb = indb;
340 sched_lastoutdb = outdb;
341 }
342 sched_nextmeterpolltime =
343 sched_diddsp + (int)(sys_dacsr /(double)sys_schedblocksize);
344}
345#endif /* ROCKBOX */
346
347void glob_meters(void *dummy, float f)
348{
349#ifdef ROCKBOX
350 (void) dummy;
351#endif
352 if (f == 0)
353 sys_getmeters(0, 0);
354 sched_meterson = (f != 0);
355 sched_lastinclip = sched_lastoutclip = sched_lastindb = sched_lastoutdb =
356 -1;
357}
358
359#if 0
360void glob_foo(void *dummy, t_symbol *s, int argc, t_atom *argv)
361{
362 if (argc) sys_clearhist();
363 else sys_printhist();
364}
365#endif
366
367void dsp_tick(void);
368
369static int sched_usedacs = 1;
370static t_time sched_referencerealtime, sched_referencelogicaltime;
371#ifndef ROCKBOX
372static
373#endif
374t_time sys_time_per_dsp_tick;
375
376void sched_set_using_dacs(int flag)
377{
378 sched_usedacs = flag;
379 if (!flag)
380 {
381 sched_referencerealtime = sys_getrealtime();
382 sched_referencelogicaltime = clock_getlogicaltime();
383#ifndef ROCKBOX
384 post("schedsetuding");
385#endif
386 }
387 sys_time_per_dsp_tick = (TIMEUNITPERSEC) *
388 ((double)sys_schedblocksize) / sys_dacsr;
389/*
390#ifdef SIMULATOR
391printf("%f\n%f\n%f\n%f\n", (double)sys_time_per_dsp_tick, (double)TIMEUNITPERSEC, (double) sys_schedblocksize, (double)sys_dacsr);
392#endif
393*/
394}
395
396 /* take the scheduler forward one DSP tick, also handling clock timeouts */
397#ifndef ROCKBOX
398static
399#endif
400void sched_tick(t_time next_sys_time)
401{
402#ifndef ROCKBOX
403 int countdown = 5000;
404#endif
405 while (clock_setlist && clock_setlist->c_settime < next_sys_time)
406 {
407 t_clock *c = clock_setlist;
408 sys_time = c->c_settime;
409 clock_unset(clock_setlist);
410 outlet_setstacklim();
411 (*c->c_fn)(c->c_owner);
412#ifndef ROCKBOX
413 if (!countdown--)
414 {
415 countdown = 5000;
416 sys_pollgui();
417 }
418#endif
419 if (sys_quit)
420 return;
421 }
422 sys_time = next_sys_time;
423 dsp_tick();
424 sched_diddsp++;
425}
426
427/*
428Here is Pd's "main loop." This routine dispatches clock timeouts and DSP
429"ticks" deterministically, and polls for input from MIDI and the GUI. If
430we're left idle we also poll for graphics updates; but these are considered
431lower priority than the rest.
432
433The time source is normally the audio I/O subsystem via the "sys_send_dacs()"
434call. This call returns true if samples were transferred; false means that
435the audio I/O system is still busy with previous transfers.
436*/
437
438void sys_pollmidiqueue( void);
439void sys_initmidiqueue( void);
440
441#ifndef ROCKBOX
442int m_scheduler_pda( void)
443{
444 int idlecount = 0;
445
446 sys_time_per_dsp_tick = (TIMEUNITPERSEC) *
447 ((double)sys_schedblocksize) / sys_dacsr;
448
449
450 sys_clearhist();
451 if (sys_sleepgrain < 1000)
452 sys_sleepgrain = sys_schedadvance/4;
453 if (sys_sleepgrain < 100)
454 sys_sleepgrain = 100;
455 else if (sys_sleepgrain > 5000)
456 sys_sleepgrain = 5000;
457
458 sys_initmidiqueue();
459
460 while (!sys_quit)
461 {
462
463 int didsomething = 0;
464
465 int timeforward;
466
467 sys_addhist(0);
468
469 waitfortick:
470 if (sched_usedacs)
471 {
472 timeforward = sys_send_dacs();
473 sys_pollgui();
474 }
475 else {
476 if ((sys_getrealtime() - sched_referencerealtime)
477 > (t_time)clock_gettimesince(sched_referencelogicaltime)*1000)
478 timeforward = SENDDACS_YES;
479 else timeforward = SENDDACS_NO;
480 if (timeforward == SENDDACS_YES)
481 sys_microsleep(sys_sleepgrain);
482 }
483 if (timeforward != SENDDACS_NO) {
484 sched_tick(sys_time + sys_time_per_dsp_tick);
485 }
486 }
487 return 0;
488}
489
490
491int m_scheduler( void)
492{
493 int idlecount = 0;
494 sys_time_per_dsp_tick = (TIMEUNITPERSEC) *
495 ((double)sys_schedblocksize) / sys_dacsr;
496
497#ifdef THREAD_LOCKING
498 /* T.Grill - lock mutex */
499 sys_lock();
500#endif
501
502 sys_clearhist();
503 if (sys_sleepgrain < 1000)
504 sys_sleepgrain = sys_schedadvance/4;
505 if (sys_sleepgrain < 100)
506 sys_sleepgrain = 100;
507 else if (sys_sleepgrain > 5000)
508 sys_sleepgrain = 5000;
509 sys_initmidiqueue();
510 while (!sys_quit)
511 {
512 int didsomething = 0;
513 int timeforward;
514
515 sys_addhist(0);
516 waitfortick:
517 if (sched_usedacs)
518 {
519 timeforward = sys_send_dacs();
520
521 /* if dacs remain "idle" for 1 sec, they're hung up. */
522 if (timeforward != 0)
523 idlecount = 0;
524 else
525 {
526 idlecount++;
527 if (!(idlecount & 31))
528 {
529 static t_time idletime;
530 /* on 32nd idle, start a clock watch; every
531 32 ensuing idles, check it */
532 if (idlecount == 32)
533 idletime = sys_getrealtime();
534 else if (sys_getrealtime() - idletime > 1.)
535 {
536 post("audio I/O stuck... closing audio\n");
537 sys_close_audio();
538 sched_set_using_dacs(0);
539 goto waitfortick;
540 }
541 }
542 }
543 }
544 else
545 {
546 if (1000. * (sys_getrealtime() - sched_referencerealtime)
547 > clock_gettimesince(sched_referencelogicaltime))
548 timeforward = SENDDACS_YES;
549 else timeforward = SENDDACS_NO;
550 }
551 sys_setmiditimediff(0, 1e-6 * sys_schedadvance);
552 sys_addhist(1);
553 if (timeforward != SENDDACS_NO)
554 sched_tick(sys_time + sys_time_per_dsp_tick);
555 if (timeforward == SENDDACS_YES)
556 didsomething = 1;
557
558 sys_addhist(2);
559 // sys_pollmidiqueue();
560 if (sys_pollgui())
561 {
562 if (!didsomething)
563 sched_didpoll++;
564 didsomething = 1;
565 }
566 sys_addhist(3);
567 /* test for idle; if so, do graphics updates. */
568 if (!didsomething)
569 {
570 sched_pollformeters();
571 sys_reportidle();
572
573#ifdef THREAD_LOCKING
574 /* T.Grill - enter idle phase -> unlock thread lock */
575 sys_unlock();
576#endif
577 if (timeforward != SENDDACS_SLEPT)
578 sys_microsleep(sys_sleepgrain);
579#ifdef THREAD_LOCKING
580 /* T.Grill - leave idle phase -> lock thread lock */
581 sys_lock();
582#endif
583
584 sys_addhist(5);
585 sched_didnothing++;
586
587 }
588 }
589
590#ifdef THREAD_LOCKING
591 /* T.Grill - done */
592 sys_unlock();
593#endif
594
595 return (0);
596}
597
598
599/* ------------ thread locking ------------------- */
600/* added by Thomas Grill */
601
602#ifdef THREAD_LOCKING
603static pthread_mutex_t sys_mutex = PTHREAD_MUTEX_INITIALIZER;
604
605void sys_lock(void)
606{
607 pthread_mutex_lock(&sys_mutex);
608}
609
610void sys_unlock(void)
611{
612 pthread_mutex_unlock(&sys_mutex);
613}
614
615int sys_trylock(void)
616{
617 return pthread_mutex_trylock(&sys_mutex);
618}
619
620#else
621
622void sys_lock(void) {}
623void sys_unlock(void) {}
624int sys_trylock(void) { return 0; }
625
626#endif
627
628
629/* ------------ soft quit ------------------- */
630/* added by Thomas Grill -
631 just set the quit flag for the scheduler loop
632 this is useful for applications using the PD shared library to signal the scheduler to terminate
633*/
634
635void sys_exit(void)
636{
637 sys_quit = 1;
638}
639
640#endif /* ROCKBOX */