supernova: fix for small audio vector sizes
[supercollider.git] / lang / LangPrimSource / PyrSched.cpp
blobf80284b59b25c1a204d051c6358bb71748976ed7
1 /*
2 SuperCollider real time audio synthesis system
3 Copyright (c) 2002 James McCartney. All rights reserved.
4 http://www.audiosynth.com
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "PyrKernel.h"
23 #include "PyrSched.h"
24 #include "GC.h"
25 #include "PyrPrimitive.h"
26 #include "PyrSymbol.h"
27 #ifdef SC_DARWIN
28 # include <CoreAudio/HostTime.h>
29 #endif
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <math.h>
33 #include <limits>
35 #ifndef SC_WIN32
36 #include <sys/time.h>
37 #endif
39 #include "SC_Win32Utils.h"
40 #include "SCBase.h"
42 static const double dInfinity = std::numeric_limits<double>::infinity();
44 void runAwakeMessage(VMGlobals *g);
46 // heaps use an integer timestamp to ensure stable heap order
47 struct PyrHeap:
48 PyrObjectHdr
50 PyrSlot count; // stability count
51 PyrSlot slots[0]; // slots
56 bool addheap(VMGlobals *g, PyrObject *heapArg, double schedtime, PyrSlot *task)
58 PyrHeap * heap = (PyrHeap*)heapArg;
59 #ifdef GC_SANITYCHECK
60 g->gc->SanityCheck();
61 #endif
62 if (heap->size >= ARRAYMAXINDEXSIZE(heap))
63 return false;
64 assert(heap->size);
66 // post("->addheap\n");
67 // dumpheap(heapArg);
69 /* parent and sibling in the heap, not in the task hierarchy */
70 int mom = heap->size - 1;
71 PyrSlot * pme = heap->slots + mom;
72 int stabilityCount = slotRawInt(&heap->count);
73 SetRaw(&heap->count, stabilityCount + 1);
75 for (; mom>0;) { /* percolate up heap */
76 int newMom = ((mom - 3) / 2);
77 mom = newMom - newMom % 3; /// LATER: we could avoid the division by using 4 slots per element
78 PyrSlot * pmom = heap->slots + mom;
79 if (schedtime < slotRawFloat(pmom)) {
80 assert(slotRawInt(pmom + 2) < stabilityCount);
81 slotCopy(&pme[0], &pmom[0]);
82 slotCopy(&pme[1], &pmom[1]);
83 slotCopy(&pme[2], &pmom[2]);
84 pme = pmom;
85 } else break;
87 SetFloat(&pme[0], schedtime);
88 slotCopy(&pme[1], task);
89 SetInt(&pme[2], stabilityCount);
90 g->gc->GCWrite(heap, task);
91 heap->size += 3;
93 #ifdef GC_SANITYCHECK
94 g->gc->SanityCheck();
95 #endif
96 // dumpheap(heapArg);
97 // post("<-addheap %g\n", schedtime);
98 return true;
102 bool lookheap(PyrObject *heap, double *schedtime, PyrSlot *task)
104 if (heap->size > 1) {
105 *schedtime = slotRawFloat(&heap->slots[0]);
106 slotCopy(task, &heap->slots[1]);
107 return true;
108 } else return false;
111 bool getheap(VMGlobals *g, PyrObject *heapArg, double *schedtime, PyrSlot *task)
113 PyrHeap * heap = (PyrHeap*)heapArg;
114 PyrGC* gc = g->gc;
115 bool isPartialScanObj = gc->IsPartialScanObject(heapArg);
116 assert(heap->size);
118 // post("->getheap\n");
119 // dumpheap(heapArg);
120 if (heap->size>1) {
121 *schedtime = slotRawFloat(&heap->slots[0]);
122 slotCopy(task, &heap->slots[1]);
123 heap->size -= 3;
124 int size = heap->size - 1;
125 slotCopy(&heap->slots[0], &heap->slots[size]);
126 slotCopy(&heap->slots[1], &heap->slots[size+1]);
127 slotCopy(&heap->slots[2], &heap->slots[size+2]);
129 /* parent and sibling in the heap, not in the task hierarchy */
130 int mom = 0;
131 int me = 3;
132 PyrSlot * pmom = heap->slots + mom;
133 PyrSlot * pme = heap->slots + me;
134 PyrSlot * pend = heap->slots + size;
135 double timetemp = slotRawFloat(&pmom[0]);
136 int stabilityCountTemp = slotRawInt(&pmom[2]);
137 PyrSlot tasktemp;
138 slotCopy(&tasktemp, &pmom[1]);
139 for (;pme < pend;) {
140 /* demote heap */
141 if (pme+3 < pend && ((slotRawFloat(&pme[0]) > slotRawFloat(&pme[3])) ||
142 ((slotRawFloat(&pme[0]) == slotRawFloat(&pme[3])) && (slotRawInt(&pme[2]) > slotRawInt(&pme[5]))
143 ))) {
144 me += 3; pme += 3;
146 if (timetemp > slotRawFloat(&pme[0]) ||
147 (timetemp == slotRawFloat(&pme[0]) && stabilityCountTemp > slotRawInt(&pme[2]))) {
148 slotCopy(&pmom[0], &pme[0]);
149 slotCopy(&pmom[1], &pme[1]);
150 slotCopy(&pmom[2], &pme[2]);
151 if (isPartialScanObj) {
152 gc->GCWriteBlack(pmom+1);
154 pmom = pme;
155 me = ((mom = me) * 2) + 3;
156 pme = heap->slots + me;
157 } else break;
159 SetRaw(&pmom[0], timetemp);
160 slotCopy(&pmom[1], &tasktemp);
161 SetRaw(&pmom[2], stabilityCountTemp);
162 if (isPartialScanObj)
163 gc->GCWriteBlack(pmom+1);
165 if (size == 0)
166 SetInt(&heap->count, 0);
168 // dumpheap(heapArg);
169 // post("<-getheap true\n");
170 return true;
171 } else {
172 // post("<-getheap false\n");
173 return false;
177 void offsetheap(VMGlobals *g, PyrObject *heap, double offset)
179 long i;
180 for (i=0; i<heap->size; i+=2) {
181 SetRaw(&heap->slots[i], slotRawFloat(&heap->slots[i]) + offset);
182 //post("%3d %9.2f %9.2f\n", i>>1, heap->slots[i].uf, offset);
186 void dumpheap(PyrObject *heapArg)
188 PyrHeap * heap = (PyrHeap*)heapArg;
189 double mintime = slotRawFloat(&heap->slots[0]);
190 int count = slotRawFloat(&heap->slots[2]);
191 int heapSize = heap->size - 1;
192 post("SCHED QUEUE (%d)\n", heapSize);
193 for (int i=0; i<heapSize; i+=3) {
194 post("%3d(%3d) %9.2f %p %d\n", i/3, i, slotRawFloat(&heap->slots[i]), slotRawObject(&heap->slots[i+1]), slotRawInt(&heap->slots[i+2]));
195 if ((slotRawFloat(&heap->slots[i]) < mintime)
196 || (slotRawFloat(&heap->slots[i]) == mintime && slotRawInt(&heap->slots[i+2]) < count )
198 post("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
204 bool gRunSched = false;
205 pthread_t gSchedThread;
206 pthread_t gResyncThread;
207 pthread_cond_t gSchedCond;
208 pthread_mutex_t gLangMutex;
210 #ifdef SC_DARWIN
211 int64 gHostOSCoffset = 0;
212 int64 gHostStartNanos = 0;
213 #endif
215 int64 gElapsedOSCoffset = 0;
217 const int32 kSECONDS_FROM_1900_to_1970 = (int32)2208988800UL; /* 17 leap years */
218 const double fSECONDS_FROM_1900_to_1970 = 2208988800.; /* 17 leap years */
220 #ifdef SC_DARWIN
221 void syncOSCOffsetWithTimeOfDay();
222 void* resyncThread(void* arg);
223 #else // !SC_DARWIN
225 #ifdef SC_WIN32
227 #else
228 # include <sys/time.h>
229 #endif
231 inline double GetTimeOfDay();
232 double GetTimeOfDay()
234 struct timeval tv;
235 gettimeofday(&tv, 0);
236 return (double)tv.tv_sec + 1.0e-6 * (double)tv.tv_usec;
238 #endif // SC_DARWIN
240 SC_DLLEXPORT_C void schedInit()
242 pthread_cond_init (&gSchedCond, NULL);
243 pthread_mutex_init (&gLangMutex, NULL);
245 #ifdef SC_DARWIN
246 syncOSCOffsetWithTimeOfDay();
247 pthread_create (&gResyncThread, NULL, resyncThread, (void*)0);
249 gHostStartNanos = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
250 gElapsedOSCoffset = (int64)(gHostStartNanos * kNanosToOSC) + gHostOSCoffset;
251 #else
252 gElapsedOSCoffset = (int64)kSECONDS_FROM_1900_to_1970 << 32;
253 #endif
256 SC_DLLEXPORT_C void schedCleanup()
258 pthread_mutex_destroy (&gLangMutex);
259 pthread_cond_destroy (&gSchedCond);
262 double bootSeconds();
263 double bootSeconds()
265 #ifdef SC_DARWIN
266 return 1e-9 * (double)AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
267 #else
268 return GetTimeOfDay();
269 #endif
272 double elapsedTime();
273 double elapsedTime()
275 #ifdef SC_DARWIN
276 return 1e-9 * (double)(AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) - gHostStartNanos);
277 #else
278 return GetTimeOfDay();
279 #endif
282 int64 ElapsedTimeToOSC(double elapsed)
284 return (int64)(elapsed * kSecondsToOSC) + gElapsedOSCoffset;
287 double OSCToElapsedTime(int64 oscTime)
289 return (double)(oscTime - gElapsedOSCoffset) * kOSCtoSecs;
292 void ElapsedTimeToTimespec(double elapsed, struct timespec *spec);
293 void ElapsedTimeToTimespec(double elapsed, struct timespec *spec)
295 int64 oscTime = ElapsedTimeToOSC(elapsed);
297 spec->tv_sec = (time_t)((oscTime >> 32) - kSECONDS_FROM_1900_to_1970);
298 spec->tv_nsec = (int32)((oscTime & 0xFFFFFFFF) * kOSCtoNanos);
301 int64 OSCTime()
303 return ElapsedTimeToOSC(elapsedTime());
306 #ifdef SC_DARWIN
307 void syncOSCOffsetWithTimeOfDay()
309 // generate a value gHostOSCoffset such that
310 // (gHostOSCoffset + systemTimeInOSCunits)
311 // is equal to gettimeofday time in OSCunits.
312 // Then if this machine is synced via NTP, we are synced with the world.
313 // more accurate way to do this??
315 struct timeval tv;
317 int64 systemTimeBefore, systemTimeAfter, diff;
318 int64 minDiff = 0x7fffFFFFffffFFFFLL;
320 // take best of several tries
321 const int numberOfTries = 8;
322 int64 newOffset = gHostOSCoffset;
323 for (int i=0; i<numberOfTries; ++i) {
324 systemTimeBefore = AudioGetCurrentHostTime();
325 gettimeofday(&tv, 0);
326 systemTimeAfter = AudioGetCurrentHostTime();
328 diff = systemTimeAfter - systemTimeBefore;
329 if (diff < minDiff) {
330 minDiff = diff;
331 // assume that gettimeofday happens halfway between AudioGetCurrentHostTime calls
332 int64 systemTimeBetween = systemTimeBefore + diff/2;
333 int64 systemTimeInOSCunits = (int64)((double)AudioConvertHostTimeToNanos(systemTimeBetween) * kNanosToOSC);
334 int64 timeOfDayInOSCunits = ((int64)(tv.tv_sec + kSECONDS_FROM_1900_to_1970) << 32)
335 + (int64)(tv.tv_usec * kMicrosToOSC);
336 newOffset = timeOfDayInOSCunits - systemTimeInOSCunits;
340 gHostOSCoffset = newOffset;
341 //postfl("gHostOSCoffset %016llX\n", gHostOSCoffset);
343 #endif
345 void schedAdd(VMGlobals *g, PyrObject* inQueue, double inSeconds, PyrSlot* inTask);
346 void schedAdd(VMGlobals *g, PyrObject* inQueue, double inSeconds, PyrSlot* inTask)
348 // gLangMutex must be locked
349 double prevTime = inQueue->size > 1 ? slotRawFloat(inQueue->slots + 1) : -1e10;
350 bool added = addheap(g, inQueue, inSeconds, inTask);
351 if (!added) post("scheduler queue is full.\n");
352 else {
353 if (isKindOfSlot(inTask, class_thread)) {
354 SetFloat(&slotRawThread(inTask)->nextBeat, inSeconds);
356 if (slotRawFloat(inQueue->slots + 1) != prevTime) {
357 //post("pthread_cond_signal\n");
358 pthread_cond_signal (&gSchedCond);
364 void doubleToTimespec(double secs, struct timespec *spec);
365 void doubleToTimespec(double secs, struct timespec *spec)
367 double isecs = floor(secs);
368 spec->tv_sec = (long)isecs;
369 spec->tv_nsec = (long)floor(1000000000. * (secs - isecs));
372 SC_DLLEXPORT_C void schedStop()
374 //printf("->schedStop\n");
375 pthread_mutex_lock (&gLangMutex);
376 if (gRunSched) {
377 gRunSched = false;
378 pthread_cond_signal (&gSchedCond);
379 pthread_mutex_unlock (&gLangMutex);
380 pthread_join(gSchedThread, 0);
381 } else {
382 pthread_mutex_unlock (&gLangMutex);
384 //printf("<-schedStop\n");
387 void schedClearUnsafe();
389 SC_DLLEXPORT_C void schedClear()
391 pthread_mutex_lock (&gLangMutex);
392 schedClearUnsafe();
393 pthread_mutex_unlock (&gLangMutex);
396 void schedClearUnsafe()
398 //postfl("->schedClear %d\n", gRunSched);
399 if (gRunSched) {
400 VMGlobals *g = gMainVMGlobals;
401 PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
402 inQueue->size = 1;
403 pthread_cond_signal (&gSchedCond);
404 //pthread_mutex_unlock (&gLangMutex);
406 //postfl("<-schedClear %d\n", gRunSched);
409 void post(const char *fmt, ...);
411 #ifdef SC_DARWIN
412 void* resyncThread(void* arg)
414 while (true) {
415 sleep(20);
416 syncOSCOffsetWithTimeOfDay();
417 gElapsedOSCoffset = (int64)(gHostStartNanos * kNanosToOSC) + gHostOSCoffset;
419 return 0;
421 #endif
423 extern bool gTraceInterpreter;
425 void* schedRunFunc(void* arg);
426 void* schedRunFunc(void* arg)
428 pthread_mutex_lock (&gLangMutex);
431 VMGlobals *g = gMainVMGlobals;
432 PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
433 //dumpObject(inQueue);
435 gRunSched = true;
436 while (true) {
437 assert(inQueue->size);
439 //postfl("wait until there is something in scheduler\n");
440 // wait until there is something in scheduler
441 while (inQueue->size == 1) {
442 //postfl("wait until there is something in scheduler\n");
443 pthread_cond_wait (&gSchedCond, &gLangMutex);
444 if (!gRunSched) goto leave;
446 //postfl("wait until an event is ready\n");
448 // wait until an event is ready
449 double elapsed;
450 do {
451 elapsed = elapsedTime();
452 if (elapsed >= slotRawFloat(inQueue->slots + 1)) break;
453 struct timespec abstime;
454 //doubleToTimespec(inQueue->slots->uf, &abstime);
455 ElapsedTimeToTimespec(slotRawFloat(inQueue->slots + 1), &abstime);
456 //postfl("wait until an event is ready\n");
457 pthread_cond_timedwait (&gSchedCond, &gLangMutex, &abstime);
458 if (!gRunSched) goto leave;
459 //postfl("time diff %g\n", elapsedTime() - inQueue->slots->uf);
460 } while (inQueue->size > 1);
462 //postfl("perform all events that are ready %d %.9f\n", inQueue->size, elapsed);
464 // perform all events that are ready
465 //postfl("perform all events that are ready\n");
466 while ((inQueue->size > 1) && elapsed >= slotRawFloat(inQueue->slots + 1)) {
467 double schedtime, delta;
468 PyrSlot task;
470 //postfl("while %.6f >= %.6f\n", elapsed, inQueue->slots->uf);
472 getheap(g, inQueue, &schedtime, &task);
474 if (isKindOfSlot(&task, class_thread)) {
475 SetNil(&slotRawThread(&task)->nextBeat);
478 slotCopy((++g->sp), &task);
479 SetFloat(++g->sp, schedtime);
480 SetFloat(++g->sp, schedtime);
481 ++g->sp; SetObject(g->sp, s_systemclock->u.classobj);
483 runAwakeMessage(g);
484 long err = slotDoubleVal(&g->result, &delta);
485 if (!err) {
486 // add delta time and reschedule
487 double time = schedtime + delta;
489 schedAdd(g, inQueue, time, &task);
492 //postfl("loop\n");
494 //postfl("exitloop\n");
495 leave:
496 pthread_mutex_unlock (&gLangMutex);
497 return 0;
500 #ifdef SC_DARWIN
501 #include <mach/mach.h>
502 #include <mach/thread_policy.h>
504 //Polls a (non-realtime) thread to find out how it is scheduled
505 //Results are undefined of an error is returned. Otherwise, the
506 //priority is returned in the address pointed to by the priority
507 //parameter, and whether or not the thread uses timeshare scheduling
508 //is returned at the address pointed to by the isTimeShare parameter
509 kern_return_t GetStdThreadSchedule( mach_port_t machThread,
510 int *priority,
511 boolean_t *isTimeshare )
513 kern_return_t result = 0;
514 thread_extended_policy_data_t timeShareData;
515 thread_precedence_policy_data_t precedenceData;
516 mach_msg_type_number_t structItemCount;
517 boolean_t fetchDefaults = false;
519 memset( &timeShareData, 0, sizeof( thread_extended_policy_data_t ));
520 memset( &precedenceData, 0, sizeof( thread_precedence_policy_data_t ));
522 if( 0 == machThread )
523 machThread = mach_thread_self();
525 if( NULL != isTimeshare )
527 structItemCount = THREAD_EXTENDED_POLICY_COUNT;
528 result = thread_policy_get( machThread, THREAD_EXTENDED_POLICY,
529 (integer_t*)&timeShareData, &structItemCount, &fetchDefaults );
530 *isTimeshare = timeShareData.timeshare;
531 if( 0 != result )
532 return result;
535 if( NULL != priority )
537 fetchDefaults = false;
538 structItemCount = THREAD_PRECEDENCE_POLICY_COUNT;
539 result = thread_policy_get( machThread, THREAD_PRECEDENCE_POLICY,
540 (integer_t*)&precedenceData, &structItemCount, &fetchDefaults );
541 *priority = precedenceData.importance;
544 return result;
547 // Reschedules the indicated thread according to new parameters:
549 // machThread The mach thread id. Pass 0 for the current thread.
550 // newPriority The desired priority.
551 // isTimeShare false for round robin (fixed) priority,
552 // true for timeshare (normal) priority
554 // A standard new thread usually has a priority of 0 and uses the
555 // timeshare scheduling scheme. Use pthread_mach_thread_np() to
556 // to convert a pthread id to a mach thread id
557 kern_return_t RescheduleStdThread( mach_port_t machThread,
558 int newPriority,
559 boolean_t isTimeshare )
561 kern_return_t result = 0;
562 thread_extended_policy_data_t timeShareData;
563 thread_precedence_policy_data_t precedenceData;
565 //Set up some variables that we need for the task
566 precedenceData.importance = newPriority;
567 timeShareData.timeshare = isTimeshare;
568 if( 0 == machThread )
569 machThread = mach_thread_self();
571 //Set the scheduling flavor. We want to do this first, since doing so
572 //can alter the priority
573 result = thread_policy_set( machThread,
574 THREAD_EXTENDED_POLICY,
575 (integer_t*)&timeShareData,
576 THREAD_EXTENDED_POLICY_COUNT );
578 if( 0 != result )
579 return result;
581 //Now set the priority
582 return thread_policy_set( machThread,
583 THREAD_PRECEDENCE_POLICY,
584 (integer_t*)&precedenceData,
585 THREAD_PRECEDENCE_POLICY_COUNT );
588 #endif // SC_DARWIN
590 #ifdef SC_LINUX
591 #include <string.h>
593 static void SC_LinuxSetRealtimePriority(pthread_t thread, int priority)
595 int policy;
596 struct sched_param param;
598 pthread_getschedparam(thread, &policy, &param);
600 policy = SCHED_FIFO;
601 const int minprio = sched_get_priority_min(policy);
602 const int maxprio = sched_get_priority_max(policy);
603 param.sched_priority = sc_clip(priority, minprio, maxprio);
605 int err = pthread_setschedparam(thread, policy, &param);
606 if (err != 0) {
607 post("Couldn't set realtime scheduling priority %d: %s\n",
608 param.sched_priority, strerror(err));
611 #endif // SC_LINUX
614 SC_DLLEXPORT_C void schedRun()
616 pthread_create (&gSchedThread, NULL, schedRunFunc, (void*)0);
618 #ifdef SC_DARWIN
619 int policy;
620 struct sched_param param;
622 //pthread_t thread = pthread_self ();
623 pthread_getschedparam (gSchedThread, &policy, &param);
624 //post("param.sched_priority %d\n", param.sched_priority);
626 policy = SCHED_RR; // round-robin, AKA real-time scheduling
628 int machprio;
629 boolean_t timeshare;
630 GetStdThreadSchedule(pthread_mach_thread_np(gSchedThread), &machprio, &timeshare);
631 //post("mach priority %d timeshare %d\n", machprio, timeshare);
633 // what priority should gSchedThread use?
635 RescheduleStdThread(pthread_mach_thread_np(gSchedThread), 62, false);
637 GetStdThreadSchedule(pthread_mach_thread_np(gSchedThread), &machprio, &timeshare);
638 //post("mach priority %d timeshare %d\n", machprio, timeshare);
640 //param.sched_priority = 70; // you'll have to play with this to see what it does
641 //pthread_setschedparam (gSchedThread, policy, &param);
643 pthread_getschedparam (gSchedThread, &policy, &param);
645 //post("param.sched_priority %d\n", param.sched_priority);
646 #endif // SC_DARWIN
648 #ifdef SC_LINUX
649 SC_LinuxSetRealtimePriority(gSchedThread, 1);
650 #endif // SC_LINUX
657 unscheduled events:
658 startup,
659 receive OSC,
660 mouse, keyboard, MIDI
662 all these happen in the main thread.
667 new clock:
668 create
669 destroy
670 wake up at time x.
671 unschedule
672 awake
673 reschedules.
677 class TempoClock
679 public:
680 TempoClock(VMGlobals *inVMGlobals, PyrObject* inTempoClockObj,
681 double inTempo, double inBaseBeats, double inBaseSeconds);
682 ~TempoClock() {}
683 void StopReq();
684 void Stop();
686 void* Run();
688 void Add(double inBeats, PyrSlot* inTask);
689 void SetTempoAtBeat(double inTempo, double inBeats);
690 void SetTempoAtTime(double inTempo, double inSeconds);
691 void SetAll(double inTempo, double inBeats, double inSeconds);
692 double ElapsedBeats();
693 void Clear();
694 //void Flush();
695 double GetTempo() const { return mTempo; }
696 double GetBeatDur() const { return mBeatDur; }
697 double BeatsToSecs(double beats) const
698 { return (beats - mBaseBeats) * mBeatDur + mBaseSeconds; }
699 double SecsToBeats(double secs) const
700 { return (secs - mBaseSeconds) * mTempo + mBaseBeats; }
701 void Dump();
703 //protected:
704 VMGlobals* g;
705 PyrObject* mTempoClockObj;
706 PyrHeap* mQueue;
708 double mTempo; // beats per second
709 double mBeatDur; // 1/tempo
710 double mBeats; // beats
711 double mBaseSeconds;
712 double mBaseBeats;
713 volatile bool mRun;
714 pthread_t mThread;
715 pthread_cond_t mCondition;
716 TempoClock *mPrev, *mNext;
718 static TempoClock *sAll;
722 TempoClock *TempoClock::sAll = 0;
725 void* TempoClock_run_func(void* p)
727 TempoClock* clock = (TempoClock*)p;
728 return clock->Run();
731 void* TempoClock_stop_func(void* p)
733 //printf("->TempoClock_stop_func\n");
734 TempoClock* clock = (TempoClock*)p;
735 clock->Stop();
736 //printf("delete\n");
737 delete clock;
738 //printf("<-TempoClock_stop_func\n");
739 return 0;
742 void TempoClock_stopAll(void)
744 //printf("->TempoClock_stopAll %p\n", TempoClock::sAll);
745 TempoClock *clock = TempoClock::sAll;
746 while (clock) {
747 TempoClock* next = clock->mNext;
748 clock->Stop();
749 //printf("delete\n");
750 delete clock;
751 clock = next;
753 //printf("<-TempoClock_stopAll %p\n", TempoClock::sAll);
754 TempoClock::sAll = 0;
757 TempoClock::TempoClock(VMGlobals *inVMGlobals, PyrObject* inTempoClockObj,
758 double inTempo, double inBaseBeats, double inBaseSeconds)
759 : g(inVMGlobals), mTempoClockObj(inTempoClockObj), mTempo(inTempo), mBeatDur(1./inTempo),
760 mBaseSeconds(inBaseSeconds), mBaseBeats(inBaseBeats), mRun(true), mPrev(0), mNext(sAll)
762 if (sAll) sAll->mPrev = this;
763 sAll = this;
765 mQueue = (PyrHeap*)slotRawObject(&mTempoClockObj->slots[0]);
766 mQueue->size = 1;
767 SetInt(&mQueue->count, 0);
768 pthread_cond_init (&mCondition, NULL);
770 int err = pthread_create (&mThread, NULL, TempoClock_run_func, (void*)this);
771 if (err)
773 post("Couldn't start thread for TempoClock: %s\n", strerror(err));
774 return;
776 #ifdef SC_DARWIN
777 int machprio;
778 boolean_t timeshare;
779 GetStdThreadSchedule(pthread_mach_thread_np(mThread), &machprio, &timeshare);
780 //post("mach priority %d timeshare %d\n", machprio, timeshare);
782 // what priority should gSchedThread use?
784 RescheduleStdThread(pthread_mach_thread_np(mThread), 10, false);
786 GetStdThreadSchedule(pthread_mach_thread_np(mThread), &machprio, &timeshare);
787 //post("mach priority %d timeshare %d\n", machprio, timeshare);
789 //param.sched_priority = 70; // you'll have to play with this to see what it does
790 //pthread_setschedparam (mThread, policy, &param);
791 #endif // SC_DARWIN
793 #ifdef SC_LINUX
794 SC_LinuxSetRealtimePriority(mThread, 1);
795 #endif // SC_LINUX
798 void TempoClock::StopReq()
800 //printf("->TempoClock::StopReq\n");
801 pthread_t stopThread;
802 pthread_create (&stopThread, NULL, TempoClock_stop_func, (void*)this);
803 pthread_detach(stopThread);
804 //printf("<-TempoClock::StopReq\n");
807 void TempoClock::Stop()
809 //printf("->TempoClock::Stop\n");
810 pthread_mutex_lock (&gLangMutex);
811 //printf("Stop mRun %d\n", mRun);
812 if (mRun) {
813 mRun = false;
815 // unlink
816 if (mPrev) mPrev->mNext = mNext;
817 else sAll = mNext;
818 if (mNext) mNext->mPrev = mPrev;
820 //printf("Stop pthread_cond_signal\n");
821 pthread_cond_signal (&mCondition);
822 //printf("Stop pthread_mutex_unlock\n");
823 pthread_mutex_unlock (&gLangMutex);
824 //printf("Stop pthread_join\n");
825 pthread_join(mThread, 0);
826 } else {
827 pthread_mutex_unlock (&gLangMutex);
829 //printf("Stop pthread_cond_destroy\n");
830 pthread_cond_destroy (&mCondition);
831 //printf("<-TempoClock::Stop\n");
834 void TempoClock::SetAll(double inTempo, double inBeats, double inSeconds)
836 mBaseSeconds = inSeconds;
837 mBaseBeats = inBeats;
838 mTempo = inTempo;
839 mBeatDur = 1. / mTempo;
840 pthread_cond_signal (&mCondition);
843 void TempoClock::SetTempoAtBeat(double inTempo, double inBeats)
845 mBaseSeconds = BeatsToSecs(inBeats);
846 mBaseBeats = inBeats;
847 mTempo = inTempo;
848 mBeatDur = 1. / mTempo;
849 pthread_cond_signal (&mCondition);
852 void TempoClock::SetTempoAtTime(double inTempo, double inSeconds)
854 mBaseBeats = SecsToBeats(inSeconds);
855 mBaseSeconds = inSeconds;
856 mTempo = inTempo;
857 mBeatDur = 1. / mTempo;
858 pthread_cond_signal (&mCondition);
861 double TempoClock::ElapsedBeats()
863 return SecsToBeats(elapsedTime());
866 void* TempoClock::Run()
868 //printf("->TempoClock::Run\n");
869 pthread_mutex_lock (&gLangMutex);
870 while (mRun) {
871 assert(mQueue->size);
872 //printf("tempo %g dur %g beats %g\n", mTempo, mBeatDur, mBeats);
873 //printf("wait until there is something in scheduler\n");
874 // wait until there is something in scheduler
875 while (mQueue->size == 1) {
876 //printf("wait until there is something in scheduler\n");
877 pthread_cond_wait (&mCondition, &gLangMutex);
878 //printf("mRun a %d\n", mRun);
879 if (!mRun) goto leave;
881 //printf("wait until an event is ready\n");
883 // wait until an event is ready
884 double elapsedBeats;
885 do {
886 elapsedBeats = ElapsedBeats();
887 if (elapsedBeats >= slotRawFloat(mQueue->slots)) break;
888 struct timespec abstime;
889 //doubleToTimespec(mQueue->slots->uf, &abstime);
890 //printf("event ready at %g . elapsed beats %g\n", mQueue->slots->uf, elapsedBeats);
891 double wakeTime = BeatsToSecs(slotRawFloat(mQueue->slots));
892 ElapsedTimeToTimespec(wakeTime, &abstime);
893 //printf("wait until an event is ready. wake %g now %g\n", wakeTime, elapsedTime());
894 pthread_cond_timedwait (&mCondition, &gLangMutex, &abstime);
895 //printf("mRun b %d\n", mRun);
896 if (!mRun) goto leave;
897 //printf("time diff %g\n", elapsedTime() - mQueue->slots->uf);
898 } while (mQueue->size > 1);
899 //printf("perform all events that are ready %d %.9f\n", mQueue->size, elapsedBeats);
901 // perform all events that are ready
902 //printf("perform all events that are ready\n");
903 while (mQueue->size > 1 && elapsedBeats >= slotRawFloat(mQueue->slots)) {
904 double delta;
905 PyrSlot task;
907 //printf("while %.6f >= %.6f\n", elapsedBeats, mQueue->slots->uf);
909 getheap(g, (PyrObject*)mQueue, &mBeats, &task);
911 if (isKindOfSlot(&task, class_thread)) {
912 SetNil(&slotRawThread(&task)->nextBeat);
915 slotCopy((++g->sp), &task);
916 SetFloat(++g->sp, mBeats);
917 SetFloat(++g->sp, BeatsToSecs(mBeats));
918 ++g->sp; SetObject(g->sp, mTempoClockObj);
920 runAwakeMessage(g);
921 long err = slotDoubleVal(&g->result, &delta);
922 if (!err) {
923 // add delta time and reschedule
924 double beats = mBeats + delta;
925 Add(beats, &task);
929 leave:
930 //printf("<-TempoClock::Run\n");
931 pthread_mutex_unlock (&gLangMutex);
932 return 0;
936 void TempoClock::Flush()
938 while (mQueue->size && elapsedBeats >= mQueue->slots->uf) {
939 double delta;
940 PyrSlot task;
942 //printf("while %.6f >= %.6f\n", elapsedBeats, mQueue->slots->uf);
944 getheap(g, mQueue, &mBeats, &task);
946 slotCopy((++g->sp), &task);
947 (++g->sp)->uf = mBeats;
948 (++g->sp)->uf = BeatsToSecs(mBeats);
949 ++g->sp; SetObject(g->sp, mTempoClockObj);
951 runAwakeMessage(g);
952 long err = slotDoubleVal(&g->result, &delta);
953 if (!err) {
954 // add delta time and reschedule
955 double beats = mBeats + delta;
956 Add(beats, &task);
963 void TempoClock::Add(double inBeats, PyrSlot* inTask)
965 double prevBeats = mQueue->size > 1 ? slotRawFloat(mQueue->slots) : -1e10;
966 bool added = addheap(g, (PyrObject*)mQueue, inBeats, inTask);
967 if (!added) post("scheduler queue is full.\n");
968 else {
969 if (isKindOfSlot(inTask, class_thread)) {
970 SetFloat(&slotRawThread(inTask)->nextBeat, inBeats);
972 if (slotRawFloat(mQueue->slots) != prevBeats) {
973 pthread_cond_signal (&mCondition);
978 void TempoClock::Clear()
980 if (mRun) {
981 mQueue->size = 1;
982 pthread_cond_signal (&mCondition);
986 void TempoClock::Dump()
988 post("mTempo %g\n", mTempo);
989 post("mBeatDur %g\n", mBeatDur);
990 post("mBeats %g\n", mBeats);
991 post("seconds %g\n", BeatsToSecs(mBeats));
992 post("mBaseSeconds %g\n", mBaseSeconds);
993 post("mBaseBeats %g\n", mBaseBeats);
996 int prTempoClock_New(struct VMGlobals *g, int numArgsPushed);
997 int prTempoClock_New(struct VMGlobals *g, int numArgsPushed)
999 PyrSlot *a = g->sp - 3;
1000 PyrSlot *b = g->sp - 2;
1001 PyrSlot *c = g->sp - 1;
1002 PyrSlot *d = g->sp;
1004 double tempo;
1005 int err = slotDoubleVal(b, &tempo);
1006 if (err) tempo = 1.;
1007 if (tempo <= 0.) {
1008 error("invalid tempo %g\n", tempo);
1009 SetPtr(slotRawObject(a)->slots+1, NULL);
1010 return errFailed;
1013 double beats;
1014 err = slotDoubleVal(c, &beats);
1015 if (err) beats = 0.;
1017 double seconds;
1018 err = slotDoubleVal(d, &seconds);
1019 if (err) seconds = elapsedTime();
1021 TempoClock* clock = new TempoClock(g, slotRawObject(a), tempo, beats, seconds);
1022 SetPtr(slotRawObject(a)->slots+1, clock);
1023 return errNone;
1026 int prTempoClock_Free(struct VMGlobals *g, int numArgsPushed);
1027 int prTempoClock_Free(struct VMGlobals *g, int numArgsPushed)
1029 PyrSlot *a = g->sp;
1030 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1031 if (!clock) return errNone; // not running
1033 SetNil(slotRawObject(a)->slots + 1);
1034 clock->StopReq();
1036 return errNone;
1039 int prTempoClock_Clear(struct VMGlobals *g, int numArgsPushed);
1040 int prTempoClock_Clear(struct VMGlobals *g, int numArgsPushed)
1042 PyrSlot *a = g->sp;
1043 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1044 if (clock) clock->Clear();
1046 return errNone;
1049 int prTempoClock_Dump(struct VMGlobals *g, int numArgsPushed);
1050 int prTempoClock_Dump(struct VMGlobals *g, int numArgsPushed)
1052 PyrSlot *a = g->sp;
1053 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1054 if (clock) clock->Dump();
1056 return errNone;
1060 int prTempoClock_Tempo(struct VMGlobals *g, int numArgsPushed);
1061 int prTempoClock_Tempo(struct VMGlobals *g, int numArgsPushed)
1063 PyrSlot *a = g->sp;
1064 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1065 if (!clock) {
1066 error("clock is not running.\n");
1067 return errFailed;
1070 SetFloat(a, clock->mTempo);
1072 return errNone;
1075 int prTempoClock_BeatDur(struct VMGlobals *g, int numArgsPushed);
1076 int prTempoClock_BeatDur(struct VMGlobals *g, int numArgsPushed)
1078 PyrSlot *a = g->sp;
1079 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1080 if (!clock) {
1081 error("clock is not running.\n");
1082 return errFailed;
1085 SetFloat(a, clock->mBeatDur);
1087 return errNone;
1090 int prTempoClock_ElapsedBeats(struct VMGlobals *g, int numArgsPushed);
1091 int prTempoClock_ElapsedBeats(struct VMGlobals *g, int numArgsPushed)
1093 PyrSlot *a = g->sp;
1094 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1095 if (!clock) {
1096 error("clock is not running.\n");
1097 return errFailed;
1100 SetFloat(a, clock->ElapsedBeats());
1102 return errNone;
1105 int prTempoClock_Beats(struct VMGlobals *g, int numArgsPushed);
1106 int prTempoClock_Beats(struct VMGlobals *g, int numArgsPushed)
1108 PyrSlot *a = g->sp;
1109 double beats, seconds;
1111 if (SlotEq(&g->thread->clock, a)) {
1112 int err = slotDoubleVal(&g->thread->beats, &beats);
1113 if (err) return errWrongType;
1114 } else {
1115 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1116 if (!clock) {
1117 error("clock is not running.\n");
1118 return errFailed;
1121 int err = slotDoubleVal(&g->thread->seconds, &seconds);
1122 if (err) return errWrongType;
1124 beats = clock->SecsToBeats(seconds);
1126 SetFloat(a, beats);
1127 return errNone;
1130 int prTempoClock_Sched(struct VMGlobals *g, int numArgsPushed);
1131 int prTempoClock_Sched(struct VMGlobals *g, int numArgsPushed)
1133 PyrSlot *a = g->sp - 2;
1134 PyrSlot *b = g->sp - 1;
1135 PyrSlot *c = g->sp;
1136 double delta, beats;
1137 int err;
1139 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1140 if (!clock) {
1141 error("clock is not running.\n");
1142 return errFailed;
1145 if (!SlotEq(&g->thread->clock, a)) {
1146 beats = clock->ElapsedBeats();
1147 //post("shouldn't call TempoClock-sched from a different clock. Use schedAbs.\n");
1148 //return errFailed;
1149 } else {
1150 err = slotDoubleVal(&g->thread->beats, &beats);
1151 if (err) return errNone; // return nil OK, just don't schedule
1154 err = slotDoubleVal(b, &delta);
1155 if (err) return errNone; // return nil OK, just don't schedule
1156 beats += delta;
1157 if (beats == dInfinity)
1158 return errNone; // return nil OK, just don't schedule
1160 clock->Add(beats, c);
1162 return errNone;
1165 int prTempoClock_SchedAbs(struct VMGlobals *g, int numArgsPushed);
1166 int prTempoClock_SchedAbs(struct VMGlobals *g, int numArgsPushed)
1168 PyrSlot *a = g->sp - 2;
1169 PyrSlot *b = g->sp - 1;
1170 PyrSlot *c = g->sp;
1172 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1173 if (!clock) {
1174 error("clock is not running.\n");
1175 return errFailed;
1178 double beats;
1179 int err = slotDoubleVal(b, &beats) || (beats == dInfinity);
1180 if (err) return errNone; // return nil OK, just don't schedule
1182 clock->Add(beats, c);
1184 return errNone;
1187 int prTempoClock_SetTempoAtBeat(struct VMGlobals *g, int numArgsPushed);
1188 int prTempoClock_SetTempoAtBeat(struct VMGlobals *g, int numArgsPushed)
1190 PyrSlot *a = g->sp - 2;
1191 PyrSlot *b = g->sp - 1;
1192 PyrSlot *c = g->sp;
1194 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1195 if (!clock) {
1196 error("clock is not running.\n");
1197 return errFailed;
1200 double tempo, beat;
1201 int err = slotDoubleVal(b, &tempo);
1202 if (err) return errFailed;
1203 if (tempo <= 0.) {
1204 error("invalid tempo %g\n", tempo);
1205 return errFailed;
1208 err = slotDoubleVal(c, &beat);
1209 if (err) return errFailed;
1211 clock->SetTempoAtBeat(tempo, beat);
1213 return errNone;
1216 int prTempoClock_SetAll(struct VMGlobals *g, int numArgsPushed);
1217 int prTempoClock_SetAll(struct VMGlobals *g, int numArgsPushed)
1219 PyrSlot *a = g->sp - 3;
1220 PyrSlot *b = g->sp - 2;
1221 PyrSlot *c = g->sp - 1;
1222 PyrSlot *d = g->sp;
1224 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1225 if (!clock) {
1226 error("clock is not running.\n");
1227 return errFailed;
1230 double tempo, beat, secs;
1231 int err = slotDoubleVal(b, &tempo);
1232 if (err) return errFailed;
1234 err = slotDoubleVal(c, &beat);
1235 if (err) return errFailed;
1237 err = slotDoubleVal(d, &secs);
1238 if (err) return errFailed;
1240 clock->SetAll(tempo, beat, secs);
1242 return errNone;
1245 int prTempoClock_SetTempoAtTime(struct VMGlobals *g, int numArgsPushed);
1246 int prTempoClock_SetTempoAtTime(struct VMGlobals *g, int numArgsPushed)
1248 PyrSlot *a = g->sp - 2;
1249 PyrSlot *b = g->sp - 1;
1250 PyrSlot *c = g->sp;
1252 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1253 if (!clock) {
1254 error("clock is not running.\n");
1255 return errFailed;
1258 double tempo, sec;
1259 int err = slotDoubleVal(b, &tempo);
1260 if (err) return errFailed;
1262 err = slotDoubleVal(c, &sec);
1263 if (err) return errFailed;
1265 clock->SetTempoAtTime(tempo, sec);
1267 return errNone;
1272 int prTempoClock_BeatsToSecs(struct VMGlobals *g, int numArgsPushed);
1273 int prTempoClock_BeatsToSecs(struct VMGlobals *g, int numArgsPushed)
1275 PyrSlot *a = g->sp - 1;
1276 PyrSlot *b = g->sp;
1278 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1279 if (!clock) {
1280 error("clock is not running.\n");
1281 return errFailed;
1284 double beats;
1285 int err = slotDoubleVal(b, &beats);
1286 if (err) return errFailed;
1288 SetFloat(a, clock->BeatsToSecs(beats));
1290 return errNone;
1293 int prTempoClock_SecsToBeats(struct VMGlobals *g, int numArgsPushed);
1294 int prTempoClock_SecsToBeats(struct VMGlobals *g, int numArgsPushed)
1296 PyrSlot *a = g->sp - 1;
1297 PyrSlot *b = g->sp;
1299 TempoClock *clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1300 if (!clock) {
1301 error("clock is not running.\n");
1302 return errFailed;
1305 double secs;
1306 int err = slotDoubleVal(b, &secs);
1307 if (err) return errFailed;
1309 SetFloat(a, clock->SecsToBeats(secs));
1311 return errNone;
1315 int prSystemClock_Clear(struct VMGlobals *g, int numArgsPushed);
1316 int prSystemClock_Clear(struct VMGlobals *g, int numArgsPushed)
1318 //PyrSlot *a = g->sp;
1320 schedClearUnsafe();
1322 return errNone;
1325 int prSystemClock_Sched(struct VMGlobals *g, int numArgsPushed);
1326 int prSystemClock_Sched(struct VMGlobals *g, int numArgsPushed)
1328 //PyrSlot *a = g->sp - 2;
1329 PyrSlot *b = g->sp - 1;
1330 PyrSlot *c = g->sp;
1332 double delta, seconds;
1333 int err = slotDoubleVal(b, &delta);
1334 if (err) return errNone; // return nil OK, just don't schedule
1335 err = slotDoubleVal(&g->thread->seconds, &seconds);
1336 if (err) return errNone; // return nil OK, just don't schedule
1337 seconds += delta;
1338 if (seconds == dInfinity) return errNone; // return nil OK, just don't schedule
1339 PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
1341 schedAdd(g, inQueue, seconds, c);
1343 return errNone;
1346 int prSystemClock_SchedAbs(struct VMGlobals *g, int numArgsPushed);
1347 int prSystemClock_SchedAbs(struct VMGlobals *g, int numArgsPushed)
1349 //PyrSlot *a = g->sp - 2;
1350 PyrSlot *b = g->sp - 1;
1351 PyrSlot *c = g->sp;
1353 double time;
1354 int err = slotDoubleVal(b, &time) || (time == dInfinity);
1355 if (err) return errNone; // return nil OK, just don't schedule
1356 PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
1358 schedAdd(g, inQueue, time, c);
1360 return errNone;
1363 int prElapsedTime(struct VMGlobals *g, int numArgsPushed);
1364 int prElapsedTime(struct VMGlobals *g, int numArgsPushed)
1366 SetFloat(g->sp, elapsedTime());
1367 return errNone;
1370 void initSchedPrimitives()
1372 int base, index=0;
1374 base = nextPrimitiveIndex();
1376 definePrimitive(base, index++, "_TempoClock_New", prTempoClock_New, 4, 0);
1377 definePrimitive(base, index++, "_TempoClock_Free", prTempoClock_Free, 1, 0);
1378 definePrimitive(base, index++, "_TempoClock_Clear", prTempoClock_Clear, 1, 0);
1379 definePrimitive(base, index++, "_TempoClock_Dump", prTempoClock_Dump, 1, 0);
1380 definePrimitive(base, index++, "_TempoClock_Sched", prTempoClock_Sched, 3, 0);
1381 definePrimitive(base, index++, "_TempoClock_SchedAbs", prTempoClock_SchedAbs, 3, 0);
1382 definePrimitive(base, index++, "_TempoClock_Tempo", prTempoClock_Tempo, 1, 0);
1383 definePrimitive(base, index++, "_TempoClock_BeatDur", prTempoClock_BeatDur, 1, 0);
1384 definePrimitive(base, index++, "_TempoClock_ElapsedBeats", prTempoClock_ElapsedBeats, 1, 0);
1385 definePrimitive(base, index++, "_TempoClock_Beats", prTempoClock_Beats, 1, 0);
1386 definePrimitive(base, index++, "_TempoClock_SetTempoAtBeat", prTempoClock_SetTempoAtBeat, 3, 0);
1387 definePrimitive(base, index++, "_TempoClock_SetTempoAtTime", prTempoClock_SetTempoAtTime, 3, 0);
1388 definePrimitive(base, index++, "_TempoClock_SetAll", prTempoClock_SetAll, 4, 0);
1389 definePrimitive(base, index++, "_TempoClock_BeatsToSecs", prTempoClock_BeatsToSecs, 2, 0);
1390 definePrimitive(base, index++, "_TempoClock_SecsToBeats", prTempoClock_SecsToBeats, 2, 0);
1392 definePrimitive(base, index++, "_SystemClock_Clear", prSystemClock_Clear, 1, 0);
1393 definePrimitive(base, index++, "_SystemClock_Sched", prSystemClock_Sched, 3, 0);
1394 definePrimitive(base, index++, "_SystemClock_SchedAbs", prSystemClock_SchedAbs, 3, 0);
1396 definePrimitive(base, index++, "_ElapsedTime", prElapsedTime, 1, 0);