A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 640 lines 15 kB view raw
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 */