@@ -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 | |||