aboutsummaryrefslogtreecommitdiff
path: root/desiredata/src/m_sched.c
diff options
context:
space:
mode:
authorIOhannes m zmölnig <zmoelnig@users.sourceforge.net>2008-02-08 13:00:32 +0000
committerIOhannes m zmölnig <zmoelnig@users.sourceforge.net>2008-02-08 13:00:32 +0000
commit4d84d14ac1aa13958eaa2971b03f7f929a519105 (patch)
tree6579d3f2cea5410a10c4baac8d0f372fb0dff372 /desiredata/src/m_sched.c
parentb334d38aefbd8e0e159d7af6c20d63c5d2b64859 (diff)
reorganized
svn path=/trunk/; revision=9400
Diffstat (limited to 'desiredata/src/m_sched.c')
-rw-r--r--desiredata/src/m_sched.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/desiredata/src/m_sched.c b/desiredata/src/m_sched.c
new file mode 100644
index 00000000..b4e0fc1a
--- /dev/null
+++ b/desiredata/src/m_sched.c
@@ -0,0 +1,654 @@
+/* Copyright (c) 1997-1999 Miller Puckette.
+* For information on usage and redistribution, and for a DISCLAIMER OF ALL
+* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */
+
+/* scheduling stuff */
+
+#include "desire.h"
+#include <stdlib.h>
+
+/* for timeval */
+#ifdef UNISTD
+#include <sys/time.h>
+#endif
+#ifdef __CYGWIN__
+#include <sys/time.h>
+#endif
+#ifdef MSW
+#include "winsock.h"
+#endif
+
+#include "assert.h"
+
+/* LATER consider making this variable. It's now the LCM of all sample
+ rates we expect to see: 32000, 44100, 48000, 88200, 96000. */
+#define TIMEUNITPERSEC (32.*441000.)
+
+/* T.Grill - enable PD global thread locking - sys_lock, sys_unlock, sys_trylock functions */
+#define THREAD_LOCKING
+#include "pthread.h"
+#include "time.h"
+
+static int sys_quit;
+double sys_time;
+static double sys_time_per_msec = TIMEUNITPERSEC / 1000.;
+
+/* tb: { */
+int sys_keepsched = 1; /* if 0: change scheduler mode */
+int sys_callbackscheduler = 0; /* if 1: change scheduler to callback based dsp */
+static void run_idle_callbacks(int microsec);
+t_fifo * callback_fifo = NULL;
+/* tb: }*/
+
+int sys_usecsincelastsleep ();
+int sys_sleepgrain;
+
+typedef void (*t_clockmethod)(void *client);
+
+#ifdef UNISTD
+#include <unistd.h>
+#endif
+
+struct _clock {
+ double settime;
+ void *owner;
+ t_clockmethod fn;
+ struct _clock *next;
+};
+
+t_clock *clock_setlist;
+t_clock *clock_new(void *owner, t_method fn) {
+ t_clock *x = (t_clock *)malloc(sizeof(*x));
+ x->settime = -1;
+ x->owner = owner;
+ x->fn = (t_clockmethod)fn;
+ x->next = 0;
+ return x;
+}
+
+void clock_unset(t_clock *x) {
+ if (x->settime >= 0) {
+ if (x == clock_setlist) clock_setlist = x->next;
+ else {
+ t_clock *x2 = clock_setlist;
+ while (x2->next != x) x2 = x2->next;
+ x2->next = x->next;
+ }
+ x->settime = -1;
+ }
+}
+
+/* set the clock to call back at an absolute system time */
+void clock_set(t_clock *x, double setticks) {
+ if (setticks < sys_time) setticks = sys_time;
+ clock_unset(x);
+ x->settime = setticks;
+ if (clock_setlist && clock_setlist->settime <= setticks) {
+ t_clock *cbefore, *cafter;
+ for (cbefore = clock_setlist, cafter = clock_setlist->next; cbefore; cbefore = cafter, cafter = cbefore->next) {
+ if (!cafter || cafter->settime > setticks) {
+ cbefore->next = x;
+ x->next = cafter;
+ return;
+ }
+ }
+ } else x->next = clock_setlist, clock_setlist = x;
+}
+
+/* set the clock to call back after a delay in msec */
+void clock_delay(t_clock *x, double delaytime) {
+ clock_set(x, sys_time + sys_time_per_msec * delaytime);
+}
+
+/* get current logical time. We don't specify what units this is in;
+ use clock_gettimesince() to measure intervals from time of this call.
+ This was previously, incorrectly named "clock_getsystime"; the old
+ name is aliased to the new one in m_pd.h. */
+double clock_getlogicaltime () {return sys_time;}
+
+/* OBSOLETE NAME */
+double clock_getsystime () {return sys_time;}
+
+/* elapsed time in milliseconds since the given system time */
+double clock_gettimesince(double prevsystime) {
+ return (sys_time - prevsystime)/sys_time_per_msec;
+}
+
+/* what value the system clock will have after a delay */
+double clock_getsystimeafter(double delaytime) {
+ return sys_time + sys_time_per_msec * delaytime;
+}
+
+void clock_free(t_clock *x) {
+ clock_unset(x);
+ free(x);
+}
+
+/* the following routines maintain a real-execution-time histogram of the
+various phases of real-time execution. */
+
+static int sys_bin[] = {0, 2, 5, 10, 20, 30, 50, 100, 1000};
+#define NBIN (sizeof(sys_bin)/sizeof(*sys_bin))
+#define NHIST 10
+static int sys_histogram[NHIST][NBIN];
+static double sys_histtime;
+static int sched_diddsp, sched_didpoll, sched_didnothing;
+
+void sys_clearhist () {
+ unsigned i,j;
+ for (i=0; i<NHIST; i++) for (j=0; j<NBIN; j++) sys_histogram[i][j] = 0;
+ sys_histtime = sys_getrealtime();
+ sched_diddsp = sched_didpoll = sched_didnothing = 0;
+}
+
+void sys_printhist () {
+ for (int i=0; i<NHIST; i++) {
+ int doit = 0;
+ for (unsigned int j=0; j<NBIN; j++) if (sys_histogram[i][j]) doit = 1;
+ if (doit) {
+ post("%2d %8d %8d %8d %8d %8d %8d %8d %8d", i,
+ sys_histogram[i][0], sys_histogram[i][1], sys_histogram[i][2], sys_histogram[i][3],
+ sys_histogram[i][4], sys_histogram[i][5], sys_histogram[i][6], sys_histogram[i][7]);
+ }
+ }
+ post("dsp %d, pollgui %d, nothing %d", sched_diddsp, sched_didpoll, sched_didnothing);
+}
+
+static int sys_histphase;
+
+int sys_addhist(int phase) {
+ int phasewas = sys_histphase;
+ double newtime = sys_getrealtime();
+ int msec = int((newtime-sys_histtime)*1000.);
+ for (int j=NBIN-1; j >= 0; j--) {
+ if (msec >= sys_bin[j]) {
+ sys_histogram[phasewas][j]++;
+ break;
+ }
+ }
+ sys_histtime = newtime;
+ sys_histphase = phase;
+ return phasewas;
+}
+
+#define NRESYNC 20
+
+struct t_resync {
+ int ntick;
+ int error;
+};
+
+static int oss_resyncphase = 0;
+static int oss_nresync = 0;
+static t_resync oss_resync[NRESYNC];
+
+static char *(oss_errornames[]) = {
+"unknown",
+"ADC blocked",
+"DAC blocked",
+"A/D/A sync",
+"data late",
+"xrun",
+"sys_lock timeout"
+};
+
+void glob_audiostatus (void *dummy) {
+ int nresync, nresyncphase, i;
+ nresync = oss_nresync >= NRESYNC ? NRESYNC : oss_nresync;
+ nresyncphase = oss_resyncphase - 1;
+ post("audio I/O error history:");
+ post("seconds ago\terror type");
+ for (i = 0; i < nresync; i++) {
+ int errtype;
+ if (nresyncphase < 0) nresyncphase += NRESYNC;
+ errtype = oss_resync[nresyncphase].error;
+ if (errtype < 0 || errtype > 4) errtype = 0;
+ post("%9.2f\t%s", (sched_diddsp - oss_resync[nresyncphase].ntick)
+ * ((double)sys_schedblocksize) / sys_dacsr, oss_errornames[errtype]);
+ nresyncphase--;
+ }
+}
+
+static int sched_diored;
+static int sched_dioredtime;
+static int sched_meterson;
+
+void sys_log_error(int type) {
+ oss_resync[oss_resyncphase].ntick = sched_diddsp;
+ oss_resync[oss_resyncphase].error = type;
+ oss_nresync++;
+ if (++oss_resyncphase == NRESYNC) oss_resyncphase = 0;
+ if (type != ERR_NOTHING && !sched_diored && (sched_diddsp >= sched_dioredtime)) {
+ sys_vgui("pdtk_pd_dio 1\n");
+ sched_diored = 1;
+ }
+ sched_dioredtime = sched_diddsp + (int)(sys_dacsr /(double)sys_schedblocksize);
+}
+
+static int sched_lastinclip, sched_lastoutclip, sched_lastindb, sched_lastoutdb;
+
+static void sched_pollformeters () {
+ int inclip, outclip, indb, outdb;
+ static int sched_nextmeterpolltime, sched_nextpingtime;
+ /* if there's no GUI but we're running in "realtime", here is
+ where we arrange to ping the watchdog every 2 seconds. */
+#ifdef __linux__
+ if (sys_hipriority && (sched_diddsp - sched_nextpingtime > 0)) {
+ glob_watchdog(0);
+ /* ping every 2 seconds */
+ sched_nextpingtime = sched_diddsp + 2*(int)(sys_dacsr /(double)sys_schedblocksize);
+ }
+#endif
+ if (sched_diddsp - sched_nextmeterpolltime < 0) return;
+ if (sched_diored && sched_diddsp-sched_dioredtime > 0) {
+ sys_vgui("pdtk_pd_dio 0\n");
+ sched_diored = 0;
+ }
+ if (sched_meterson) {
+ float inmax, outmax;
+ sys_getmeters(&inmax, &outmax);
+ indb = int(0.5 + rmstodb(inmax));
+ outdb = int(0.5 + rmstodb(outmax));
+ inclip = inmax > 0.999;
+ outclip = outmax >= 1.0;
+ } else {
+ indb = outdb = 0;
+ inclip = outclip = 0;
+ }
+ if (inclip != sched_lastinclip || outclip != sched_lastoutclip
+ || indb != sched_lastindb || outdb != sched_lastoutdb) {
+ sys_vgui("pdtk_pd_meters %d %d %d %d\n", indb, outdb, inclip, outclip);
+ sched_lastinclip = inclip;
+ sched_lastoutclip = outclip;
+ sched_lastindb = indb;
+ sched_lastoutdb = outdb;
+ }
+ sched_nextmeterpolltime = sched_diddsp + (int)(sys_dacsr /(double)sys_schedblocksize);
+}
+
+void glob_meters(void *dummy, float f) {
+ if (f == 0) sys_getmeters(0, 0);
+ sched_meterson = (f != 0);
+ sched_lastinclip = sched_lastoutclip = sched_lastindb = sched_lastoutdb = -1;
+}
+
+#if 0
+void glob_foo(void *dummy, t_symbol *s, int argc, t_atom *argv) {
+ if (argc) sys_clearhist();
+ else sys_printhist();
+}
+#endif
+
+extern void dsp_tick ();
+
+static int sched_usedacs = 0;
+static double sched_referencerealtime, sched_referencelogicaltime;
+double sys_time_per_dsp_tick;
+
+void sched_set_using_dacs(int flag) {
+ sched_usedacs = flag;
+ if (!flag) {
+ sched_referencerealtime = sys_getrealtime();
+ sched_referencelogicaltime = clock_getlogicaltime();
+ }
+ sys_time_per_dsp_tick = (TIMEUNITPERSEC) * ((double)sys_schedblocksize) / sys_dacsr;
+}
+
+static void run_clock_callbacks(double next_sys_time) {
+ if (clock_setlist && clock_setlist->settime <= next_sys_time) {
+ do {
+ t_clock *c = clock_setlist;
+ sys_time = c->settime;
+ clock_unset(c); /* the compiler should easily inline this */
+ outlet_setstacklim();
+ c->fn(c->owner);
+ } while (clock_setlist && clock_setlist->settime <= next_sys_time);
+ }
+}
+
+/* take the scheduler forward one DSP tick, also handling clock timeouts */
+void sched_tick(double next_sys_time) {
+ run_clock_callbacks(next_sys_time);
+ sys_time = next_sys_time;
+ sched_diddsp++; /* rethink: how to get rid of this stupid histogram??? */
+ dsp_tick();
+ /* rethink: should we really do all this midi messaging in the realtime thread ? */
+ sys_pollmidiqueue();
+ sys_setmiditimediff(0, 1e-6 * sys_schedadvance);
+}
+
+
+/*
+Here is Pd's "main loop." This routine dispatches clock timeouts and DSP
+"ticks" deterministically, and polls for input from MIDI and the GUI. If
+we're left idle we also poll for graphics updates; but these are considered
+lower priority than the rest.
+
+The time source is normally the audio I/O subsystem via the "sys_send_dacs()"
+call. This call returns true if samples were transferred; false means that
+the audio I/O system is still busy with previous transfers.
+*/
+
+void sys_pollmidiqueue ();
+void sys_initmidiqueue ();
+
+void canvas_stop_dsp ();
+
+int m_scheduler () {
+ int idlecount = 0;
+ sys_time_per_dsp_tick = (TIMEUNITPERSEC) * ((double)sys_schedblocksize) / sys_dacsr;
+ /* T.Grill - lock mutex */
+ sys_lock();
+ sys_clearhist();
+ /* tb: adapt sleepgrain with advance */
+ sys_update_sleepgrain();
+ sched_set_using_dacs(0); /* tb: dsp is switched off */
+ sys_initmidiqueue();
+ while (!sys_quit) {
+ if (!sys_callbackscheduler || !sched_usedacs)
+ while (sys_keepsched) {
+ int didsomething = 0;
+ int timeforward;
+ waitfortick:
+ if (sched_usedacs) {
+ timeforward = sys_send_dacs();
+ /* if dacs remain "idle" for 1 sec, they're hung up. */
+ if (timeforward != 0)
+ idlecount = 0;
+ else {
+ idlecount++;
+ if (!(idlecount & 31)) {
+ static double idletime;
+ /* on 32nd idle, start a clock watch; every 32 ensuing idles, check it */
+ if (idlecount == 32) idletime = sys_getrealtime();
+ else if (sys_getrealtime() - idletime > 1.) {
+ post("audio I/O stuck... closing audio");
+ sys_close_audio();
+ sched_set_using_dacs(0);
+ canvas_stop_dsp(); /* added by matju 2007.06.30 */
+ goto waitfortick;
+ }
+ }
+ }
+ } else {
+ if (1000. * (sys_getrealtime() - sched_referencerealtime) > clock_gettimesince(sched_referencelogicaltime))
+ timeforward = SENDDACS_YES;
+ else timeforward = SENDDACS_NO;
+ }
+ sys_setmiditimediff(0, 1e-6 * sys_schedadvance);
+ if (timeforward != SENDDACS_NO) sched_tick(sys_time + sys_time_per_dsp_tick);
+ if (timeforward == SENDDACS_YES) didsomething = 1;
+ sys_pollmidiqueue();
+ if (sys_pollgui()) didsomething = 1;
+ /* test for idle; if so, do graphics updates. */
+ if (!didsomething) {
+ sched_pollformeters();
+ /* tb: call idle callbacks */
+ if (timeforward != SENDDACS_SLEPT) run_idle_callbacks(sys_sleepgrain);
+ }
+ }
+ else /* tb: scheduler for callback-based dsp scheduling */
+ while(sys_keepsched) {
+ /* tb: allow the audio callback to run */
+ sys_unlock();
+ sys_microsleep(sys_sleepgrain);
+ sys_lock();
+ sys_pollmidiqueue();
+ sys_setmiditimediff(0, 1e-6 * sys_schedadvance);
+ if (sys_pollgui()) continue;
+ /* do graphics updates and run idle callbacks */
+ sched_pollformeters();
+ }
+ sys_keepsched = 1;
+ }
+ sys_close_audio();
+ sys_unlock();
+ return 0;
+}
+
+/* ------------ thread locking ------------------- */
+/* added by Thomas Grill */
+
+#ifdef THREAD_LOCKING
+static pthread_mutex_t sys_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t sys_cond = PTHREAD_COND_INITIALIZER;
+
+void sys_lock () {
+ pthread_mutex_lock(&sys_mutex);
+}
+void sys_unlock () {
+ pthread_mutex_unlock(&sys_mutex);
+ pthread_cond_signal(&sys_cond);
+}
+int sys_trylock () {
+ return pthread_mutex_trylock(&sys_mutex);
+}
+
+/* tb { */
+#include <errno.h>
+
+#ifdef MSW
+/* gettimeofday isn't available on windoze ... */
+int gettimeofday (struct timeval *tv, void* tz) {
+ __int64 now; /* time since 1 Jan 1601 in 100ns */
+ GetSystemTimeAsFileTime ((FILETIME*) &now);
+ tv->tv_usec = (long) ((now / 10LL) % 1000000LL);
+ tv->tv_sec = (long) ((now - 116444736000000000LL) / 10000000LL);
+ return 0;
+}
+#endif
+
+#if 0
+/* osx doesn't define a pthread_mutex_timedlock ... maybe someday
+ it will ... */
+int sys_timedlock(int microsec) {
+ struct timespec timeout;
+ struct timeval now;
+ /* timedlock seems to have a resolution of 1ms */
+ if (microsec < 1000) microsec = 1000;
+ gettimeofday(&now,0);
+ timeout.tv_sec = now.tv_sec;
+ timeout.tv_nsec = (now.tv_usec + microsec) * 1000;
+ while (timeout.tv_nsec > 1e9) {
+ timeout.tv_sec += 1;
+ timeout.tv_nsec -= 1e9;
+ }
+ int ret = pthread_mutex_timedlock(&sys_mutex, &timeout);
+ if (ret) post("timeout, %d", ret);
+ return ret;
+}
+#else
+
+int sys_timedlock(int microsec) {
+ struct timespec timeout;
+ struct timeval now;
+ if (sys_trylock() == 0) return 0;
+ if (microsec < 1000) microsec = 1000;
+ gettimeofday(&now,0);
+ timeout.tv_sec = now.tv_sec;
+ timeout.tv_nsec = (now.tv_usec + microsec) * 1000;
+ while (timeout.tv_nsec > 1000000000) {
+ timeout.tv_sec += 1;
+ timeout.tv_nsec -= 1000000000;
+ }
+ /* in case the lock has been released during the system call, try
+ again before waiting for the signal */
+ if (sys_trylock() == 0) return 0;
+ return pthread_cond_timedwait(&sys_cond, &sys_mutex, &timeout);
+}
+#endif
+/* tb } */
+
+#else
+void sys_lock () {}
+void sys_unlock () {}
+int sys_trylock () { return 0; }
+int sys_timedlock (int microsec) { return 0; }
+#endif
+
+/* ------------ soft quit ------------------- */
+/* added by Thomas Grill -
+ just set the quit flag for the scheduler loop
+ this is useful for applications using the PD shared library to signal the scheduler to terminate
+*/
+
+void sys_exit () {
+ sys_keepsched = 0;
+ sys_quit = 1;
+}
+
+/* tb: place callbacks in scheduler
+ * { */
+/* linked list of callbacks; callback will be freed after returning 0 */
+struct t_sched_callback {
+ struct t_sched_callback *next; /* next callback in ringbuffer / in fifo */
+ t_int (*function)(t_int *argv);
+ t_int *argv;
+ t_int argc;
+};
+
+void sys_callback(t_int (*callback)(t_int* argv), t_int* argv, t_int argc) {
+ t_sched_callback* noo = (t_sched_callback *)malloc(sizeof(t_sched_callback));
+ noo->function = callback;
+ if (argv && argc) {
+ noo->argv = (t_int*) copybytes (argv, argc * sizeof (t_int));
+ noo->argc = argc;
+ } else {
+ noo->argc = 0;
+ noo->argv = 0;
+ }
+ noo->next = 0;
+ if (!callback_fifo) callback_fifo = fifo_init();
+ fifo_put(callback_fifo, noo);
+}
+
+void sys_init_idle_callbacks () {
+ callback_fifo = fifo_init(); /* tb: initialize fifo for idle callbacks */
+}
+
+static t_sched_callback *ringbuffer_head = NULL;
+
+void run_all_idle_callbacks () {
+ t_sched_callback *new_callback;
+ /* append idle callback to ringbuffer */
+ while ((new_callback = (t_sched_callback*) fifo_get(callback_fifo))) {
+ t_sched_callback *next;
+ /* set the next field to 0 ... it might be set in the fifo */
+ new_callback->next = 0;
+ if (!ringbuffer_head) {
+ ringbuffer_head = new_callback;
+ } else {
+ next = ringbuffer_head;
+ while (next->next) next = next->next;
+ next->next = new_callback;
+ }
+ }
+ if (ringbuffer_head) {
+ t_sched_callback *idle_callback = ringbuffer_head;
+ t_sched_callback *last = 0;
+ t_sched_callback *next;
+ do {
+ int status;
+ status = (idle_callback->function)(idle_callback->argv);
+ switch (status) {
+ /* callbacks returning 0 will be deleted */
+ case 0:
+ next = idle_callback->next;
+ if (idle_callback->argv) free(idle_callback->argv);
+ free((void*)idle_callback);
+ if (!last) ringbuffer_head = next; else last->next = next;
+ idle_callback = next;
+ /* callbacks returning 1 will be run again */
+ case 1:
+ break;
+ /* callbacks returning 2 will be run during the next idle callback */
+ case 2:
+ last = idle_callback;
+ idle_callback = idle_callback->next;
+ }
+ } while (idle_callback);
+ }
+}
+
+static void run_idle_callbacks(int microsec) {
+ t_sched_callback *new_callback;
+ double stop = sys_getrealtime()*1.e6 + (double)microsec;
+ /* append idle callback to ringbuffer */
+ while ((new_callback = (t_sched_callback*) fifo_get(callback_fifo))) {
+ /* set the next field to NULL ... it might be set in the fifo */
+ new_callback->next = 0;
+ if (!ringbuffer_head) {
+ ringbuffer_head = new_callback;
+ } else {
+ t_sched_callback *next = ringbuffer_head;
+ while (next->next != 0)
+ next = next->next;
+ next->next = new_callback;
+ }
+ }
+ if (ringbuffer_head) {
+ double remain = stop - sys_getrealtime() * 1.e6;
+ t_sched_callback *idle_callback = ringbuffer_head;
+ t_sched_callback *last = 0;
+ t_sched_callback *next;
+ do {
+// sys_lock();
+ int status = idle_callback->function(idle_callback->argv);
+// sys_unlock();
+ switch (status) {
+ /* callbacks returning 0 will be deleted */
+ case 0:
+ next = idle_callback->next;
+ if (idle_callback->argc) free(idle_callback->argv);
+ free((void*)idle_callback);
+ if (!last) ringbuffer_head = next; else last->next = next;
+ idle_callback = next;
+ /* callbacks returning 1 will be run again */
+ case 1:
+ break;
+ /* callbacks returning 2 will be run during the next idle callback */
+ case 2:
+ last = idle_callback;
+ idle_callback = idle_callback->next;
+ }
+ remain = stop-sys_getrealtime()*1.e6;
+ } while (idle_callback && remain>0);
+ /* sleep for the rest of the time */
+ if(remain > 0) {
+ sys_unlock();
+ sys_microsleep(int(remain));
+ sys_lock();
+ }
+ } else {
+ sys_unlock();
+ sys_microsleep(microsec);
+ sys_lock();
+ }
+}
+/* } tb */
+
+void sys_setscheduler(int scheduler) {
+ sys_keepsched = 0;
+ sys_callbackscheduler = scheduler;
+ return;
+}
+
+int sys_getscheduler () {return sys_callbackscheduler;}
+
+static t_int sys_xrun_notification_callback(t_int *dummy) {
+ t_symbol *pd = gensym("pd");
+ t_symbol *xrun = gensym("xrun");
+ typedmess(pd->s_thing, xrun, 0, 0);
+ return 0;
+}
+
+void sys_xrun_notification () {sys_callback(sys_xrun_notification_callback, 0, 0);}
+
+static t_int sys_lock_timeout_notification_callback(t_int *dummy) {
+ t_symbol *pd = gensym("pd");
+ t_symbol *timeout = gensym("sys_lock_timeout");
+ typedmess(pd->s_thing, timeout, 0, 0);
+ return 0;
+}
+
+void sys_lock_timeout_notification () {sys_callback(sys_lock_timeout_notification_callback, 0, 0);}