diff options
Diffstat (limited to 'interface/render.py')
-rw-r--r-- | interface/render.py | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/interface/render.py b/interface/render.py new file mode 100644 index 0000000..5b6c934 --- /dev/null +++ b/interface/render.py @@ -0,0 +1,130 @@ +import threading +import time + +import mpv + +from blc2.workspace import Workspace +from blc2.topology import Fixture + +class Renderer: + def hold(self, values): + with self._lock: + for c, v in values: + if v is None: + if c in self._hold: + del self._hold[c] + else: + self._hold[c] = v + ## Rely on the run thread to update when possible + if not self._running: + ## If not, just update now + self._update() + + def _update(self): + with self._lock: + self._values = {c: (v if c not in self._hold else self._hold[c]) for c, v in self._last.items()} + self.output.set_values(self._values) + + def start(self): + with self._lock: + if self._run_thread is not None: + raise ValueError("Already running") + self._run_thread = threading.Thread(target=self._run) + self._running = True + self._run_thread.start() + + def stop(self): + with self._lock: + self._running = False + + def set_functions(self, f): + with self._lock: + if self._running: + raise ValueError("Can't change while running") + self._functions, self._data = [i[0] for i in f], [i[1] for i in f] + + @property + def time(self): + with self._lock: + return self._current + + def _run(self): + audio_cache = {} + + sleep = 1/60 + next_ap = [] + ap = [] + running_ap = set() + t = 0 + start = time.monotonic() + while True: + ## w_lock here is a formality: by assumption, we're not editing while we're + ## running a show + with self._lock, self.w_lock: + self._update() + for a in next_ap: + a.pause = False + next_ap = [] + + if self._callback is not None: + self._callback(t, self._values) + + ## FIXME: Cleanup finished audio players? + ## FIXME: Handle audio fades and jumps + + next_t = sleep + time.monotonic() + self._current = next_t - start + t = int(1000*self._current) + _last = {c: 0 for c in self._channels} + for n, (f, d) in enumerate(zip(self._functions, self._data)): + lc, ac, self._data[n] = f.render(t, d) + for c, v in lc: + if _last[c] < v: + _last[c] = v + + for guid, filename, *_ in ac: + if guid in running_ap: + continue + running_ap.add(guid) + nap = mpv.MPV() + nap.pause = True + nap.play(filename) + next_ap.append(nap) + ap.append(nap) + + self._last = _last + self._update() + + if not self._running: + ## We're done, clean up + for a in ap: + a.pause = True + del a + self._last = {c: 0 for c in self._channels} + self._current = 0 + self._update() + if self._callback is not None: + self._callback(0, self._values) + self._run_thread = None + break + + ## END locked block + time.sleep(max(0, next_t - time.monotonic())) + + def __init__(self, w: Workspace, w_lock: threading.RLock, output, callback=None): + self.output = output + self.w = w + self.w_lock = w_lock + + self._lock = threading.RLock() + self._hold = {} + self._channels = frozenset().union(*((c for c in f.channels) for f in w.fixtures.values())) + self._last = {c: 0 for c in self._channels} + self._functions = [] + self._data = [] + self._current = 0 + self._running = False + self._run_thread = None + self._values = {} + + self._callback = callback |