diff --git a/common/JackAPI.cpp b/common/JackAPI.cpp index e0ca05b4..6db5e34b 100644 --- a/common/JackAPI.cpp +++ b/common/JackAPI.cpp @@ -180,10 +180,15 @@ extern "C" LIB_EXPORT int jack_engine_takeover_timebase(jack_client_t *); LIB_EXPORT jack_nframes_t jack_frames_since_cycle_start(const jack_client_t *); LIB_EXPORT jack_time_t jack_get_time(); - LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t time); + LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t usecs); LIB_EXPORT jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t frames); LIB_EXPORT jack_nframes_t jack_frame_time(const jack_client_t *); LIB_EXPORT jack_nframes_t jack_last_frame_time(const jack_client_t *client); + LIB_EXPORT int jack_get_cycle_times(const jack_client_t *client, + jack_nframes_t *current_frames, + jack_time_t *current_usecs, + jack_time_t *next_usecs, + float *period_usecs); LIB_EXPORT float jack_cpu_load(jack_client_t *client); LIB_EXPORT jack_native_thread_t jack_client_thread_id(jack_client_t *); LIB_EXPORT void jack_set_error_function(print_function); @@ -1331,7 +1336,7 @@ LIB_EXPORT jack_time_t jack_frames_to_time(const jack_client_t* ext_client, jack } } -LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t* ext_client, jack_time_t time) +LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t* ext_client, jack_time_t usecs) { JackGlobals::CheckContext("jack_time_to_frames"); @@ -1344,7 +1349,7 @@ LIB_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t* ext_client, j JackEngineControl* control = GetEngineControl(); if (control) { control->ReadFrameTime(&timer); - return timer.Time2Frames(time, control->fBufferSize); + return timer.Time2Frames(usecs, control->fBufferSize); } else { return 0; } @@ -1366,6 +1371,24 @@ LIB_EXPORT jack_nframes_t jack_last_frame_time(const jack_client_t* ext_client) return (control) ? control->fFrameTimer.ReadCurrentState()->CurFrame() : 0; } +LIB_EXPORT int jack_get_cycle_times(const jack_client_t *client, + jack_nframes_t *current_frames, + jack_time_t *current_usecs, + jack_time_t *next_usecs, + float *period_usecs) +{ + JackGlobals::CheckContext("jack_get_cycle_times"); + + JackEngineControl* control = GetEngineControl(); + if (control) { + JackTimer timer; + control->ReadFrameTime(&timer); + return timer.GetCycleTimes(current_frames, current_usecs, next_usecs, period_usecs); + } else { + return 1; + } +} + LIB_EXPORT float jack_cpu_load(jack_client_t* ext_client) { JackGlobals::CheckContext("jack_cpu_load"); diff --git a/common/JackEngineControl.cpp b/common/JackEngineControl.cpp index 4af6f68d..cd1adad4 100644 --- a/common/JackEngineControl.cpp +++ b/common/JackEngineControl.cpp @@ -97,10 +97,11 @@ void JackEngineControl::ResetRollingUsecs() void JackEngineControl::NotifyXRun(jack_time_t callback_usecs, float delayed_usecs) { - ResetFrameTime(callback_usecs); + ResetFrameTime(callback_usecs); // Is this still necessary? SL 03/21/2012 fXrunDelayedUsecs = delayed_usecs; - if (delayed_usecs > fMaxDelayedUsecs) + if (delayed_usecs > fMaxDelayedUsecs) { fMaxDelayedUsecs = delayed_usecs; + } } } // end of namespace diff --git a/common/JackFrameTimer.cpp b/common/JackFrameTimer.cpp index 180de970..2c153b03 100644 --- a/common/JackFrameTimer.cpp +++ b/common/JackFrameTimer.cpp @@ -43,14 +43,26 @@ JackTimer::JackTimer() fCurrentWakeup = 0; fCurrentCallback = 0; fNextWakeUp = 0; - fFilterCoefficient = 0.01f; - fSecondOrderIntegrator = 0.0f; + fPeriodUsecs = 0.0f; + fFilterOmega = 0.0f; /* Initialised later */ } -jack_nframes_t JackTimer::Time2Frames(jack_time_t time, jack_nframes_t buffer_size) +jack_nframes_t JackTimer::Time2Frames(jack_time_t usecs, jack_nframes_t buffer_size) { if (fInitialized) { - return fFrames + (long)rint(((double) ((long long)(time - fCurrentWakeup)) / ((long long)(fNextWakeUp - fCurrentWakeup))) * buffer_size); + /* + Make sure we have signed differences. It would make a lot of sense + to use the standard signed intNN_t types everywhere instead of e.g. + jack_nframes_t and jack_time_t. This would at least ensure that the + types used below are the correct ones. There is no way to get a type + that would be 'a signed version of jack_time_t' for example - the + types below are inherently fragile and there is no automatic way to + check they are the correct ones. The only way is to check manually + against jack/types.h. FA - 16/02/2012 + */ + int64_t du = usecs - fCurrentWakeup; + int64_t dp = fNextWakeUp - fCurrentWakeup; + return fFrames + (int32_t)rint((double)du / (double)dp * buffer_size); } else { return 0; } @@ -59,12 +71,37 @@ jack_nframes_t JackTimer::Time2Frames(jack_time_t time, jack_nframes_t buffer_si jack_time_t JackTimer::Frames2Time(jack_nframes_t frames, jack_nframes_t buffer_size) { if (fInitialized) { - return fCurrentWakeup + (long)rint(((double) ((long long)(frames - fFrames)) * ((long long)(fNextWakeUp - fCurrentWakeup))) / buffer_size); + /* + Make sure we have signed differences. It would make a lot of sense + to use the standard signed intNN_t types everywhere instead of e.g. + jack_nframes_t and jack_time_t. This would at least ensure that the + types used below are the correct ones. There is no way to get a type + that would be 'a signed version of jack_time_t' for example - the + types below are inherently fragile and there is no automatic way to + check they are the correct ones. The only way is to check manually + against jack/types.h. FA - 16/02/2012 + */ + int32_t df = frames - fFrames; + int64_t dp = fNextWakeUp - fCurrentWakeup; + return fCurrentWakeup + (int64_t)rint((double) df * (double) dp / buffer_size); } else { return 0; } } +int JackTimer::GetCycleTimes(jack_nframes_t* current_frames, jack_time_t* current_usecs, jack_time_t* next_usecs, float* period_usecs) +{ + if (fInitialized) { + *current_frames = fFrames; + *current_usecs = fCurrentWakeup; + *next_usecs = fNextWakeUp; + *period_usecs = fPeriodUsecs; + return 0; + } else { + return 1; + } +} + jack_nframes_t JackTimer::FramesSinceCycleStart(jack_time_t cur_time, jack_nframes_t frames_rate) { return (jack_nframes_t) floor((((float)frames_rate) / 1000000.0f) * (cur_time - fCurrentCallback)); @@ -80,9 +117,9 @@ void JackFrameTimer::IncFrameTime(jack_nframes_t buffer_size, jack_time_t callba if (fFirstWakeUp) { InitFrameTimeAux(callback_usecs, period_usecs); fFirstWakeUp = false; - } else { - IncFrameTimeAux(buffer_size, callback_usecs, period_usecs); } + + IncFrameTimeAux(buffer_size, callback_usecs, period_usecs); } void JackFrameTimer::ResetFrameTime(jack_nframes_t frames_rate, jack_time_t callback_usecs, jack_time_t period_usecs) @@ -119,10 +156,44 @@ void JackFrameTimer::ReadFrameTime(JackTimer* timer) void JackFrameTimer::InitFrameTimeAux(jack_time_t callback_usecs, jack_time_t period_usecs) { + /* the first wakeup or post-freewheeling or post-xrun */ + + /* There seems to be no significant difference between + the two conditions OR-ed above. Incrementing the + frame_time after an xrun shouldn't harm, as there + will be a discontinuity anyway. So the two are + combined in this version. + FA 16/03/2012 + */ + /* Since the DLL *will* be run, next_wakeup should be the + current wakeup time *without* adding the period time, as + if it were computed in the previous period. + FA 16/03/2012 + */ + /* Added initialisation of timer->period_usecs, required + due to the modified implementation of the DLL itself. + OTOH, this should maybe not be repeated after e.g. + freewheeling or an xrun, as the current value would be + more accurate than the nominal one. But it doesn't really + harm either. Implementing this would require a new flag + in the engine structure, to be used after freewheeling + or an xrun instead of first_wakeup. I don't know if this + can be done without breaking compatibility, so I did not + add this + FA 13/02/2012 + */ + /* Added initialisation of timer->filter_omega. This makes + the DLL bandwidth independent of the actual period time. + The bandwidth is now 1/8 Hz in all cases. The value of + timer->filter_omega is 2 * pi * BW * Tperiod. + FA 13/02/2012 + */ + JackTimer* timer = WriteNextStateStart(); - timer->fSecondOrderIntegrator = 0.0f; + timer->fPeriodUsecs = (float)period_usecs; timer->fCurrentCallback = callback_usecs; - timer->fNextWakeUp = callback_usecs + period_usecs; + timer->fNextWakeUp = callback_usecs; + timer->fFilterOmega = period_usecs * 7.854e-7f; WriteNextStateStop(); TrySwitchState(); // always succeed since there is only one writer } @@ -130,13 +201,38 @@ void JackFrameTimer::InitFrameTimeAux(jack_time_t callback_usecs, jack_time_t pe void JackFrameTimer::IncFrameTimeAux(jack_nframes_t buffer_size, jack_time_t callback_usecs, jack_time_t period_usecs) { JackTimer* timer = WriteNextStateStart(); - float delta = (int64_t)callback_usecs - (int64_t)timer->fNextWakeUp; + + /* Modified implementation (the actual result is the same). + + 'fSecondOrderIntegrator' is renamed to 'fPeriodUsecs' + and now represents the DLL's best estimate of the + period time in microseconds (before it was a scaled + version of the difference w.r.t. the nominal value). + This allows this value to be made available to clients + that are interested in it (see jack_get_cycle_times). + This change also means that 'fPeriodUsecs' must be + initialised to the nominal period time instead of zero. + This is done in the first cycle in jack_run_cycle(). + + 'fFilterCoefficient' is renamed to 'fFilterOmega'. It + is now equal to the 'omega' value as defined in the + 'Using a DLL to filter time' paper (before it was a + scaled version of this value). It is computed once in + jack_run_cycle() rather than set to a fixed value. This + makes the DLL bandwidth independent of the period time. + + FA 13/02/2012 + */ + + float delta = (float)((int64_t)callback_usecs - (int64_t)timer->fNextWakeUp); + delta *= timer->fFilterOmega; timer->fCurrentWakeup = timer->fNextWakeUp; timer->fCurrentCallback = callback_usecs; timer->fFrames += buffer_size; - timer->fSecondOrderIntegrator += 0.5f * timer->fFilterCoefficient * delta; - timer->fNextWakeUp = timer->fCurrentWakeup + period_usecs + (int64_t) floorf((timer->fFilterCoefficient * (delta + timer->fSecondOrderIntegrator))); + timer->fPeriodUsecs += timer->fFilterOmega * delta; + timer->fNextWakeUp += (int64_t)floorf(timer->fPeriodUsecs + 1.41f * delta + 0.5f); timer->fInitialized = true; + WriteNextStateStop(); TrySwitchState(); // always succeed since there is only one writer } diff --git a/common/JackFrameTimer.h b/common/JackFrameTimer.h index 3ab3e153..a9c2b7a6 100644 --- a/common/JackFrameTimer.h +++ b/common/JackFrameTimer.h @@ -44,8 +44,8 @@ class SERVER_EXPORT JackTimer jack_time_t fCurrentWakeup; jack_time_t fCurrentCallback; jack_time_t fNextWakeUp; - float fSecondOrderIntegrator; - float fFilterCoefficient; /* set once, never altered */ + float fPeriodUsecs; + float fFilterOmega; /* set once, never altered */ bool fInitialized; public: @@ -57,6 +57,7 @@ class SERVER_EXPORT JackTimer jack_nframes_t Time2Frames(jack_time_t time, jack_nframes_t buffer_size); jack_time_t Frames2Time(jack_nframes_t frames, jack_nframes_t buffer_size); jack_nframes_t FramesSinceCycleStart(jack_time_t cur_time, jack_nframes_t frames_rate); + int GetCycleTimes(jack_nframes_t* current_frames, jack_time_t* current_usecs, jack_time_t* next_usecs, float* period_usecs); jack_nframes_t CurFrame() { diff --git a/common/jack/jack.h b/common/jack/jack.h index f2c3a394..7efa7c1f 100644 --- a/common/jack/jack.h +++ b/common/jack/jack.h @@ -1284,6 +1284,55 @@ jack_nframes_t jack_frame_time (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT */ jack_nframes_t jack_last_frame_time (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; +/** + * This function may only be used from the process callback. + * It provides the internal cycle timing information as used by + * most of the other time related functions. This allows the + * caller to map between frame counts and microseconds with full + * precision (i.e. without rounding frame times to integers), + * and also provides e.g. the microseconds time of the start of + * the current cycle directly (it has to be computed otherwise). + * + * If the return value is zero, the following information is + * provided in the variables pointed to by the arguments: + * + * current_frames: the frame time counter at the start of the + * current cycle, same as jack_last_frame_time(). + * current_usecs: the microseconds time at the start of the + * current cycle. + * next_usecs: the microseconds time of the start of the next + * next cycle as computed by the DLL. + * period_usecs: the current best estimate of the period time in + * microseconds. + * + * NOTES: + * + * Because of the types used, all the returned values except period_usecs + * are unsigned. In computations mapping between frames and microseconds + * *signed* differences are required. The easiest way is to compute those + * separately and assign them to the appropriate signed variables, + * int32_t for frames and int64_t for usecs. See the implementation of + * jack_frames_to_time() and Jack_time_to_frames() for an example. + * + * Unless there was an xrun, skipped cycles, or the current cycle is the + * first after freewheeling or starting Jack, the value of current_usecs + * will always be the value of next_usecs of the previous cycle. + * + * The value of period_usecs will in general NOT be exactly equal to + * the difference of next_usecs and current_usecs. This is because to + * ensure stability of the DLL and continuity of the mapping, a fraction + * of the loop error must be included in next_usecs. For an accurate + * mapping between frames and microseconds, the difference of next_usecs + * and current_usecs should be used, and not period_usecs. + * + * @return zero if OK, non-zero otherwise. + */ +int jack_get_cycle_times(const jack_client_t *client, + jack_nframes_t *current_frames, + jack_time_t *current_usecs, + jack_time_t *next_usecs, + float *period_usecs) JACK_OPTIONAL_WEAK_EXPORT; + /** * @return the estimated time in microseconds of the specified frame time */ diff --git a/tests/test.cpp b/tests/test.cpp index 95ae88ad..ceb0b897 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -444,7 +444,7 @@ static void* jack_thread(void *arg) jack_nframes_t last_thread_time = jack_frame_time(client); while (1) { - jack_nframes_t frames = jack_cycle_wait (client); + jack_nframes_t frames = jack_cycle_wait(client); jack_nframes_t current_thread_time = jack_frame_time(client); jack_nframes_t delta_time = current_thread_time - last_thread_time; Log("jack_thread : delta_time = %ld\n", delta_time); @@ -489,6 +489,36 @@ int process4(jack_nframes_t nframes, void *arg) return 0; } +int process5(jack_nframes_t nframes, void *arg) +{ + jack_client_t* client = (jack_client_t*) arg; + + static jack_nframes_t first_current_frames; + static jack_time_t first_current_usecs; + static jack_time_t first_next_usecs; + static float first_period_usecs; + static int res1 = jack_get_cycle_times(client, &first_current_frames, &first_current_usecs, &first_next_usecs, &first_period_usecs); + + jack_nframes_t current_frames; + jack_time_t current_usecs; + jack_time_t next_usecs; + float period_usecs; + + int res = jack_get_cycle_times(client, ¤t_frames, ¤t_usecs, &next_usecs, &period_usecs); + if (res != 0) { + printf("!!! ERROR !!! jack_get_cycle_times fails...\n"); + return 0; + } + + Log("calling process5 callback : jack_get_cycle_times delta current_frames = %ld delta current_usecs = %ld delta next_usecs = %ld period_usecs = %f\n", + current_frames - first_current_frames, current_usecs - first_current_usecs, next_usecs - first_next_usecs, period_usecs); + + first_current_frames = current_frames; + first_current_usecs = current_usecs; + first_next_usecs = next_usecs; + return 0; +} + static void display_transport_state() { jack_transport_state_t ts; @@ -1967,7 +1997,16 @@ int main (int argc, char *argv[]) jack_set_process_callback(client1, process4, client1); jack_activate(client1); jack_sleep(2 * 1000); - + + /** + * Checking jack_get_cycle_times. + */ + Log("Testing jack_get_cycle_times...\n"); + jack_deactivate(client1); + jack_set_process_callback(client1, process5, client1); + jack_activate(client1); + jack_sleep(3 * 1000); + /** * Checking alternate thread model