Skip to content

Commit 0933562

Browse files
committed
Add support for slow-sync clients
1 parent ae3a0b1 commit 0933562

File tree

3 files changed

+176
-5
lines changed

3 files changed

+176
-5
lines changed

examples/slow_sync.py

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env python3
2+
"""Show how slow-sync clients work."""
3+
import time
4+
5+
import jack
6+
7+
slow = jack.Client('Slow')
8+
9+
slow.pos = 0
10+
slow.ready_at = None
11+
slow.seek_time = 0.1
12+
13+
14+
@slow.set_sync_callback
15+
def slow_sync_callback(state, pos):
16+
now = time.time()
17+
print(jack.TransportState(state), pos.frame, end=' ')
18+
if state == jack.STOPPED:
19+
if slow.pos == pos.frame:
20+
print('ready')
21+
return True
22+
if slow.ready_at is None:
23+
slow.ready_at = now + slow.seek_time
24+
if slow.ready_at < now:
25+
print('just got ready')
26+
slow.pos = pos.frame
27+
slow.ready_at = None
28+
return True
29+
print('will be ready in {:.2f} seconds'.format(slow.ready_at - now))
30+
elif state == jack.STARTING:
31+
if slow.pos == pos.frame:
32+
print('ready')
33+
return True
34+
if slow.ready_at is None:
35+
slow.ready_at = now + slow.seek_time
36+
if slow.ready_at < now:
37+
print('just got ready')
38+
slow.pos = pos.frame
39+
slow.ready_at = None
40+
return True
41+
print('will be ready in {:.2f} seconds'.format(slow.ready_at - now))
42+
return False
43+
elif state == jack.ROLLING:
44+
assert slow.pos != pos.frame
45+
assert slow.ready_at is not None
46+
if slow.ready_at < now:
47+
print('just caught up')
48+
slow.pos = pos.frame
49+
slow.ready_at = None
50+
return True
51+
print('will catch up in {:.2f} seconds'.format(slow.ready_at - now))
52+
return False
53+
else:
54+
assert False
55+
return False
56+
57+
58+
# Make sure transport is stopped and at 0 initially
59+
slow.transport_stop()
60+
slow.transport_frame = 0
61+
62+
print('== setting custom sync timeout')
63+
slow.set_sync_timeout(int(0.5 * slow.seek_time * 1000 * 1000))
64+
65+
with slow:
66+
time.sleep(0.1) # Time to show STOPPED
67+
print('== starting transport')
68+
slow.transport_start()
69+
time.sleep(0.1) # Time to show STARTING
70+
print('== stopping transport')
71+
slow.transport_stop()
72+
print('== seeking to 123')
73+
slow.transport_frame = 123
74+
time.sleep(0.5 * slow.seek_time)
75+
print('== starting transport')
76+
slow.transport_start()
77+
time.sleep(slow.seek_time)
78+
print('== stopping transport')
79+
slow.transport_stop()
80+
print('== setting default sync timeout')
81+
slow.set_sync_timeout(2 * 1000 * 1000)
82+
print('== seeking to 123')
83+
slow.transport_frame = 123
84+
time.sleep(0.5 * slow.seek_time)
85+
print('== starting transport')
86+
slow.transport_start()
87+
time.sleep(slow.seek_time)
88+
slow.transport_stop()

jack_build.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
/* _jack_position: see below in "packed" section */
8080
typedef struct _jack_position jack_position_t;
8181
typedef void (*JackTimebaseCallback)(jack_transport_state_t state, jack_nframes_t nframes, jack_position_t *pos, int new_pos, void *arg);
82+
typedef int (*JackSyncCallback)(jack_transport_state_t state, jack_position_t *pos, void *arg);
8283
/* deprecated: jack_transport_bits_t */
8384
/* deprecated: jack_transport_info_t */
8485
@@ -208,8 +209,8 @@
208209
/* transport.h */
209210
210211
/* TODO: jack_release_timebase */
211-
/* TODO: jack_set_sync_callback */
212-
/* TODO: jack_set_sync_timeout */
212+
int jack_set_sync_callback(jack_client_t* client, JackSyncCallback sync_callback, void* arg);
213+
int jack_set_sync_timeout(jack_client_t* client, jack_time_t timeout);
213214
int jack_set_timebase_callback(jack_client_t* client, int conditional, JackTimebaseCallback timebase_callback, void* arg);
214215
int jack_transport_locate(jack_client_t* client, jack_nframes_t frame);
215216
jack_transport_state_t jack_transport_query(const jack_client_t* client, jack_position_t* pos);

src/jack.py

+85-3
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,9 @@ def transport_reposition_struct(self, position):
540540
effect in two process cycles. If there are slow-sync clients
541541
and the transport is already rolling, it will enter the
542542
`STARTING` state and begin invoking their sync callbacks
543-
(see `jack_set_sync_callback()`__) until ready.
543+
(see `set_sync_callback()`) until ready.
544544
This function is realtime-safe.
545545
546-
__ http://jackaudio.org/files/docs/html/group__TransportControl.html
547-
548546
Parameters
549547
----------
550548
position : jack_position_t
@@ -559,6 +557,29 @@ def transport_reposition_struct(self, position):
559557
_check(_lib.jack_transport_reposition(self._ptr, position),
560558
'Error re-positioning transport')
561559

560+
def set_sync_timeout(self, timeout):
561+
"""Set the timeout value for slow-sync clients.
562+
563+
This timeout prevents unresponsive slow-sync clients from
564+
completely halting the transport mechanism. The default is two
565+
seconds. When the timeout expires, the transport starts
566+
rolling, even if some slow-sync clients are still unready.
567+
The *sync callbacks* of these clients continue being invoked,
568+
giving them a chance to catch up.
569+
570+
Parameters
571+
----------
572+
timeout : int
573+
Delay (in microseconds) before the timeout expires.
574+
575+
See Also
576+
--------
577+
set_sync_callback
578+
579+
"""
580+
_check(_lib.jack_set_sync_timeout(self._ptr, timeout),
581+
'Error setting sync timeout')
582+
562583
def set_freewheel(self, onoff):
563584
"""Start/Stop JACK's "freewheel" mode.
564585
@@ -1175,6 +1196,67 @@ def callback_wrapper(_):
11751196
self._ptr, callback_wrapper, _ffi.NULL),
11761197
'Error setting xrun callback')
11771198

1199+
def set_sync_callback(self, callback):
1200+
"""Register (or unregister) as a slow-sync client.
1201+
1202+
A slow-sync client is one that cannot respond immediately to
1203+
transport position changes.
1204+
1205+
The *callback* will be invoked at the first available
1206+
opportunity after its registration is complete. If the client
1207+
is currently active this will be the following process cycle,
1208+
otherwise it will be the first cycle after calling `activate()`.
1209+
After that, it runs whenever some client requests a new
1210+
position, or the transport enters the `STARTING` state.
1211+
While the client is active, this callback is invoked just before
1212+
the *process callback* (see `set_process_callback()`) in the
1213+
same thread.
1214+
1215+
Clients that don't set a *sync callback* are assumed to be ready
1216+
immediately any time the transport wants to start.
1217+
1218+
Parameters
1219+
----------
1220+
callback : callable or None
1221+
1222+
User-supplied function that returns ``True`` when the
1223+
slow-sync client is ready. This realtime function must not
1224+
wait. It must have this signature::
1225+
1226+
callback(state: int, pos: jack_position_t) -> bool
1227+
1228+
The *state* argument will be:
1229+
1230+
- `STOPPED` when a new position is requested;
1231+
- `STARTING` when the transport is waiting to start;
1232+
- `ROLLING` when the timeout has expired, and the position
1233+
is now a moving target.
1234+
1235+
The *pos* argument holds the new transport position using
1236+
the same structure as returned by
1237+
`transport_query_struct()`.
1238+
1239+
Setting *callback* to ``None`` declares that this
1240+
client no longer requires slow-sync processing.
1241+
1242+
See Also
1243+
--------
1244+
set_sync_timeout
1245+
1246+
"""
1247+
if callback is None:
1248+
callback_wrapper = _ffi.NULL
1249+
else:
1250+
1251+
@self._callback('JackSyncCallback', error=False)
1252+
def callback_wrapper(state, pos, _):
1253+
return callback(state, pos)
1254+
1255+
_check(
1256+
_lib.jack_set_sync_callback(
1257+
self._ptr, callback_wrapper, _ffi.NULL),
1258+
'Error setting sync callback')
1259+
11781260
def set_timebase_callback(self, callback=None, conditional=False):
11791261
"""Register as timebase master for the JACK subsystem.
11801262

0 commit comments

Comments
 (0)