Skip to content

Commit 31ceb09

Browse files
committed
feat: add monotonic time tracking to jack client
1 parent 0f02f36 commit 31ceb09

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

matron/src/jack_client.c

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
#include <pthread.h>
12
#include <stdatomic.h>
3+
#include <stdbool.h>
4+
#include <stdint.h>
25
#include <stdio.h>
36

47
#include <jack/jack.h>
@@ -10,6 +13,14 @@ double jack_sample_rate;
1013

1114
_Atomic uint32_t xrun_count = 0;
1215

16+
// maintain a 64-bit frame counter from jack's 32-bit frame time.
17+
// extends wraparound at 48khz from ~25 hours to millions of years.
18+
static uint64_t g_last_total_frames = 0ULL;
19+
20+
// protects access to g_last_total_frames.
21+
// a mutex is safe here as time is not read from the audio thread.
22+
static pthread_mutex_t g_time_lock = PTHREAD_MUTEX_INITIALIZER;
23+
1324
static int xrun_callback(void *arg) {
1425
(void)arg;
1526
atomic_fetch_add(&xrun_count, 1);
@@ -50,8 +61,51 @@ uint32_t jack_client_get_xrun_count() {
5061
}
5162

5263
double jack_client_get_current_time() {
53-
return (double)jack_frame_time(jack_client) / jack_sample_rate;
64+
jack_nframes_t cur_low = jack_frame_time(jack_client);
65+
66+
const unsigned low_bits = (unsigned)(sizeof(jack_nframes_t) * 8u);
67+
const uint64_t low_mask = (low_bits == 64u) ? UINT64_MAX : ((1ULL << low_bits) - 1ULL);
68+
69+
pthread_mutex_lock(&g_time_lock);
70+
71+
uint64_t prev = g_last_total_frames;
72+
uint64_t prev_low = prev & low_mask;
73+
uint64_t prev_high = prev & ~low_mask;
74+
75+
uint64_t cur_low_u64 = (uint64_t)cur_low;
76+
uint64_t candidate = prev_high | cur_low_u64;
77+
78+
// detect wrap and carry to high bits.
79+
// a wrap is a large decrease (prev in upper half, cur in lower).
80+
if (low_bits < 64u && cur_low_u64 < prev_low) {
81+
const uint64_t half = (1ULL << (low_bits - 1));
82+
if (prev_low >= half && cur_low_u64 < half) {
83+
candidate += (1ULL << low_bits);
84+
} else {
85+
// ignore small decreases (out-of-order reads)
86+
candidate = prev;
87+
}
88+
}
89+
90+
// never move backwards
91+
if (candidate < prev) {
92+
candidate = prev;
93+
}
94+
95+
g_last_total_frames = candidate;
96+
97+
pthread_mutex_unlock(&g_time_lock);
98+
99+
return (double)candidate / jack_sample_rate;
100+
}
101+
102+
#ifdef NORNS_TEST
103+
void jack_client_test_reset_time_state(void) {
104+
pthread_mutex_lock(&g_time_lock);
105+
g_last_total_frames = 0ULL;
106+
pthread_mutex_unlock(&g_time_lock);
54107
}
108+
#endif
55109

56110
const char **jack_client_get_input_ports() {
57111
return jack_get_ports(jack_client, NULL, NULL, JackPortIsInput);

matron/src/jack_client.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ extern void jack_client_deinit();
1515
// returns ratio in [0,1]
1616
extern float jack_client_get_cpu_load();
1717

18-
// update and return estimate of undderun count since last query
18+
// update and return estimate of underrun count since last query
1919
extern uint32_t jack_client_get_xrun_count();
2020

21-
// get JACks current system time estimate in seconds (computed from sample frames)
21+
// get system time in seconds derived from JACK frames.
22+
// tracks wraparound to maintain a monotonic 64-bit frame counter.
2223
extern double jack_client_get_current_time();
2324

2425
extern const char **jack_client_get_input_ports();

0 commit comments

Comments
 (0)