| @@ -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"); | |||
| @@ -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 | |||
| @@ -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 | |||
| } | |||
| @@ -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() | |||
| { | |||
| @@ -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 | |||
| */ | |||
| @@ -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 | |||