Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two small bugfixes #262

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
58e6168
feat: propagate link start/stop to sardine transport
thegamecracks Jun 17, 2024
fc4c2aa
feat: try to compensate link start phase differences
thegamecracks Jun 18, 2024
7c51f1a
fix: accumulate time shift to prevent fast-forwarding
thegamecracks Jun 18, 2024
a5de026
feat: propagate sardine transport to link start/stop
thegamecracks Jun 18, 2024
38b3294
fix: AttributeError from not calling setIsPlaying() on session state
thegamecracks Jun 18, 2024
c1d98c4
fix: commit session after calling setIsPlaying()
thegamecracks Jun 18, 2024
c5c19f4
feat: WIP attempt to sync beat with link session
thegamecracks Jun 18, 2024
da77b18
feat: reset runner iterators when setting LinkClock.beat
thegamecracks Jun 18, 2024
8686488
feat: propagate link start/stop to sardine transport
thegamecracks Jun 17, 2024
942853f
feat: try to compensate link start phase differences
thegamecracks Jun 18, 2024
fd01438
fix: accumulate time shift to prevent fast-forwarding
thegamecracks Jun 18, 2024
4f6ece8
feat: propagate sardine transport to link start/stop
thegamecracks Jun 18, 2024
d3c37d0
fix: AttributeError from not calling setIsPlaying() on session state
thegamecracks Jun 18, 2024
79863ed
fix: commit session after calling setIsPlaying()
thegamecracks Jun 18, 2024
449d46f
feat: WIP attempt to sync beat with link session
thegamecracks Jun 18, 2024
c524865
feat: reset runner iterators when setting LinkClock.beat
thegamecracks Jun 18, 2024
27ee02c
Merge branch 'link-start-stop-sync' of github.com:Bubobubobubobubo/sa…
Bubobubobubobubo Jun 19, 2024
80dadce
Bump LinkPython-extern version number
Bubobubobubobubo Jun 19, 2024
4e98343
Feat: filter function signatures to prevent error when passing to new…
Bubobubobubobubo Jun 19, 2024
7db01b9
Feat: remove default variable assignation (p)
Bubobubobubobubo Jun 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ dependencies = [
"ziffers>=0.0.1",
"parsimonious>=0.10.0",
"Click>=8.1.3",
"LinkPython-extern>=1.0.4",
"LinkPython-extern>=1.1.0a1",
"python-rtmidi>=1.4.9",
"inquirerpy>=0.3.4",
"easing-functions>=1.0.4",
Expand Down
57 changes: 52 additions & 5 deletions sardine_core/clock/link_clock.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ def __init__(
self._internal_time: float = 0.0
self._last_capture: Optional[link.SessionState] = None
self._phase: float = 0.0
self._link_phase: float = 0.0
self._playing: bool = False
self._tempo: float = float(tempo)
self._tidal_nudge: int = 0
self._link_time: int = 0
self._beats_per_cycle: int = 4
self._framerate: float = 1 / 20
self._paused_link_phase: float = 0.0
self._time_shift: float = 0.0

## VORTEX ################################################

Expand Down Expand Up @@ -87,6 +90,12 @@ def bar(self) -> int:
def beat(self) -> int:
return self._beat + int(self.beat_shift)

@beat.setter
def beat(self, beat: int) -> None:
self._last_capture.requestBeatAtTime(beat, self._link_time, self.beats_per_bar)
self._link.commitSessionState(self._last_capture)
self.env.dispatch("reset_iterator", 0)

@property
def beat_duration(self) -> float:
return self._beat_duration
Expand Down Expand Up @@ -142,7 +151,7 @@ def tempo(self, new_tempo: float) -> None:

## METHODS ##############################################################

def _capture_link_info(self):
def _capture_link_info(self, *, update_transport: bool):
s: link.SessionState = self._link.captureSessionState()
self._last_capture = s
self._link_time: int = self._link.clock().micros()
Expand All @@ -151,26 +160,64 @@ def _capture_link_info(self):
playing: bool = s.isPlaying()
tempo: float = s.tempo()

self._internal_time = self._link_time / 1_000_000
self._internal_time = self._link_time / 1_000_000 + self._time_shift
self._beat = int(beat)
self._beat_duration = 60 / tempo
# Sardine phase is typically defined from 0.0 to the beat duration.
# Conversions are needed for the phase coming from the LinkClock.
self._link_phase = phase
self._phase = phase % 1 * self.beat_duration
self._playing = playing
self._playing, last_playing = playing, self._playing
self._tempo = tempo

if update_transport:
if playing and not last_playing:
self.env.resume()
elif not playing and last_playing:
self.env.pause()

def before_loop(self):
self._time_shift = 0.0

self._link = link.Link(self._tempo)
self._link.enabled = True
self._link.startStopSyncEnabled = True

# Set the origin at the start
self._capture_link_info()
self._capture_link_info(update_transport=False)
self._internal_origin = self.internal_time

def loop(self):
self._capture_link_info()
self._capture_link_info(update_transport=True)

def after_loop(self):
self._link = None

def hook(self, event: str, *args):
super().hook(event, *args)

if self._link is None:
return
elif event == "pause":
# Remember the current phase so the next time the clock is resumed,
# we can rewind time so the phase appears continuous.
self._paused_link_phase = self._link_phase
elif event == "resume":
# Alternative formula: (-bpb + lp - plp) % -bpb
delta = (self._link_phase - self._paused_link_phase) % self.beats_per_bar
if delta > 0:
# Don't allow time to jump forward, rewind instead.
delta -= self.beats_per_bar

self._time_shift += delta * self.beat_duration

if self._last_capture is not None:
# Allow boradcasting start/stop from sardine transport methods.
# This will cause a second pause/resume call from _capture_link_info(),
# but Sardine's transport methods are idempotent so it should be fine.
if event == "pause" and self._playing:
self._last_capture.setIsPlaying(False, self._link_time)
self._link.commitSessionState(self._last_capture)
elif event == "resume" and not self._playing:
self._last_capture.setIsPlaying(True, self._link_time)
self._link.commitSessionState(self._last_capture)
1 change: 1 addition & 0 deletions sardine_core/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
p = Player(name=player)
globals()[player] = p
bowl.add_handler(p)
del p

# Extensions
# An extension configuration file contains the following fields:
Expand Down
5 changes: 4 additions & 1 deletion sardine_core/scheduler/async_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,12 +737,15 @@ async def _call_func(self, func, args, kwargs):
"""Calls the given function and optionally applies time shift
according to the `defer_beats` attribute.
"""
signature = inspect.signature(func)
valid_kwargs = {k: v for k, v in kwargs.items() if k in signature.parameters}

if self.defer_beats:
delta = self.clock.time - self._expected_time
shift = self.defer_beats * self.clock.beat_duration - delta
self.time.shift += shift

return await maybe_coro(func, *args, **kwargs)
return await maybe_coro(func, *args, **valid_kwargs)

def _get_period(self, state: Optional[FunctionState]) -> Union[float, int]:
"""
Expand Down
3 changes: 3 additions & 0 deletions sardine_core/scheduler/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,6 @@ def hook(self, event: str, *args):
self.reset()
if event == "tempo_change":
self._react_to_tempo_change(*args)
if event == "reset_iterator":
for runner in self.runners:
runner.iter = args[0]