@@ -180,10 +180,15 @@ extern "C" | |||||
LIB_EXPORT int jack_engine_takeover_timebase(jack_client_t *); | 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_nframes_t jack_frames_since_cycle_start(const jack_client_t *); | ||||
LIB_EXPORT jack_time_t jack_get_time(); | 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_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_frame_time(const jack_client_t *); | ||||
LIB_EXPORT jack_nframes_t jack_last_frame_time(const jack_client_t *client); | 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 float jack_cpu_load(jack_client_t *client); | ||||
LIB_EXPORT jack_native_thread_t jack_client_thread_id(jack_client_t *); | LIB_EXPORT jack_native_thread_t jack_client_thread_id(jack_client_t *); | ||||
LIB_EXPORT void jack_set_error_function(print_function); | 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"); | 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(); | JackEngineControl* control = GetEngineControl(); | ||||
if (control) { | if (control) { | ||||
control->ReadFrameTime(&timer); | control->ReadFrameTime(&timer); | ||||
return timer.Time2Frames(time, control->fBufferSize); | |||||
return timer.Time2Frames(usecs, control->fBufferSize); | |||||
} else { | } else { | ||||
return 0; | 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; | 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) | LIB_EXPORT float jack_cpu_load(jack_client_t* ext_client) | ||||
{ | { | ||||
JackGlobals::CheckContext("jack_cpu_load"); | JackGlobals::CheckContext("jack_cpu_load"); | ||||
@@ -97,10 +97,11 @@ void JackEngineControl::ResetRollingUsecs() | |||||
void JackEngineControl::NotifyXRun(jack_time_t callback_usecs, float delayed_usecs) | 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; | fXrunDelayedUsecs = delayed_usecs; | ||||
if (delayed_usecs > fMaxDelayedUsecs) | |||||
if (delayed_usecs > fMaxDelayedUsecs) { | |||||
fMaxDelayedUsecs = delayed_usecs; | fMaxDelayedUsecs = delayed_usecs; | ||||
} | |||||
} | } | ||||
} // end of namespace | } // end of namespace |
@@ -43,14 +43,26 @@ JackTimer::JackTimer() | |||||
fCurrentWakeup = 0; | fCurrentWakeup = 0; | ||||
fCurrentCallback = 0; | fCurrentCallback = 0; | ||||
fNextWakeUp = 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) { | 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 { | } else { | ||||
return 0; | 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) | jack_time_t JackTimer::Frames2Time(jack_nframes_t frames, jack_nframes_t buffer_size) | ||||
{ | { | ||||
if (fInitialized) { | 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 { | } else { | ||||
return 0; | 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) | 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)); | 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) { | if (fFirstWakeUp) { | ||||
InitFrameTimeAux(callback_usecs, period_usecs); | InitFrameTimeAux(callback_usecs, period_usecs); | ||||
fFirstWakeUp = false; | 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) | 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) | 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(); | JackTimer* timer = WriteNextStateStart(); | ||||
timer->fSecondOrderIntegrator = 0.0f; | |||||
timer->fPeriodUsecs = (float)period_usecs; | |||||
timer->fCurrentCallback = callback_usecs; | timer->fCurrentCallback = callback_usecs; | ||||
timer->fNextWakeUp = callback_usecs + period_usecs; | |||||
timer->fNextWakeUp = callback_usecs; | |||||
timer->fFilterOmega = period_usecs * 7.854e-7f; | |||||
WriteNextStateStop(); | WriteNextStateStop(); | ||||
TrySwitchState(); // always succeed since there is only one writer | 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) | void JackFrameTimer::IncFrameTimeAux(jack_nframes_t buffer_size, jack_time_t callback_usecs, jack_time_t period_usecs) | ||||
{ | { | ||||
JackTimer* timer = WriteNextStateStart(); | 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->fCurrentWakeup = timer->fNextWakeUp; | ||||
timer->fCurrentCallback = callback_usecs; | timer->fCurrentCallback = callback_usecs; | ||||
timer->fFrames += buffer_size; | 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; | timer->fInitialized = true; | ||||
WriteNextStateStop(); | WriteNextStateStop(); | ||||
TrySwitchState(); // always succeed since there is only one writer | TrySwitchState(); // always succeed since there is only one writer | ||||
} | } | ||||
@@ -44,8 +44,8 @@ class SERVER_EXPORT JackTimer | |||||
jack_time_t fCurrentWakeup; | jack_time_t fCurrentWakeup; | ||||
jack_time_t fCurrentCallback; | jack_time_t fCurrentCallback; | ||||
jack_time_t fNextWakeUp; | jack_time_t fNextWakeUp; | ||||
float fSecondOrderIntegrator; | |||||
float fFilterCoefficient; /* set once, never altered */ | |||||
float fPeriodUsecs; | |||||
float fFilterOmega; /* set once, never altered */ | |||||
bool fInitialized; | bool fInitialized; | ||||
public: | public: | ||||
@@ -57,6 +57,7 @@ class SERVER_EXPORT JackTimer | |||||
jack_nframes_t Time2Frames(jack_time_t time, jack_nframes_t buffer_size); | 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_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); | 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() | 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; | 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 | * @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); | jack_nframes_t last_thread_time = jack_frame_time(client); | ||||
while (1) { | 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 current_thread_time = jack_frame_time(client); | ||||
jack_nframes_t delta_time = current_thread_time - last_thread_time; | jack_nframes_t delta_time = current_thread_time - last_thread_time; | ||||
Log("jack_thread : delta_time = %ld\n", delta_time); | Log("jack_thread : delta_time = %ld\n", delta_time); | ||||
@@ -489,6 +489,36 @@ int process4(jack_nframes_t nframes, void *arg) | |||||
return 0; | 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() | static void display_transport_state() | ||||
{ | { | ||||
jack_transport_state_t ts; | jack_transport_state_t ts; | ||||
@@ -1967,7 +1997,16 @@ int main (int argc, char *argv[]) | |||||
jack_set_process_callback(client1, process4, client1); | jack_set_process_callback(client1, process4, client1); | ||||
jack_activate(client1); | jack_activate(client1); | ||||
jack_sleep(2 * 1000); | 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 | * Checking alternate thread model | ||||