summaryrefslogtreecommitdiff
path: root/workspace.py
diff options
context:
space:
mode:
Diffstat (limited to 'workspace.py')
-rwxr-xr-xworkspace.py85
1 files changed, 44 insertions, 41 deletions
diff --git a/workspace.py b/workspace.py
index ed40634..c9f9a35 100755
--- a/workspace.py
+++ b/workspace.py
@@ -146,15 +146,13 @@ def ffprobe_audio_length(f, path="ffprobe"):
## END Utility functions
## BEGIN Topology classes
-
class Fixture:
"""Class representing a single light fixture.
May be composed of multiple channels.
"""
def __hash__(self):
- return hash((self.name, self.address_start, self.channel_count, self.mode, self.id,
- self.universe))
+ return self._hash
def __repr__(self):
return "Fixture(id=%d, name=%s, universe=%d, start=%d, channels=%d)" % (self.id, self.name, self.universe.id, self.address_start, self.channel_count)
@@ -166,13 +164,15 @@ class Fixture:
self.mode = mode
self.universe = universe
self.id = id_
+ self._hash = hash((self.name, self.address_start, self.channel_count, self.mode,
+ self.id, self.universe))
self.channels = [Channel(self, i) for i in range(channels)]
class Channel:
"""Class representing a single output channel."""
def __hash__(self):
- return hash((self.fixture, self.offset, self.address))
-
+ return self._hash
+
def __repr__(self):
return "Channel(address=%d)" % (self.address)
@@ -183,12 +183,13 @@ class Channel:
self.offset = offset
self.address = self.fixture.address_start + offset
self.universe = self.fixture.universe
+ self._hash = hash((self.fixture, self.offset, self.address))
class ChannelGroup:
"""Class representing a group of output channels."""
def __hash__(self):
- return hash(self.id, self.name, self.channels)
-
+ return self._hash
+
def __repr__(self):
return "ChannelGroup(id=%d, name=%s, channels=(%s))" % (self.id, self.name,
", ".join((repr(c) for c in self.channels)))
@@ -198,10 +199,12 @@ class ChannelGroup:
self.name = name
self.channels = tuple(channels)
+ self._hash = hash((self.id, self.name, self.channels))
+
class Universe:
"""Class representing an output universe."""
def __hash__(self):
- return hash((self.id, self.name))
+ return self._hash
def __repr__(self):
return "Universe(id=%d, name=%s)" % (self.id, self.name)
@@ -210,6 +213,8 @@ class Universe:
self.id = id_
self.name = name
+ self._hash = hash((self.id, self.name))
+
## END Toplogy classes
## BEGIN Base classes
@@ -235,8 +240,7 @@ class Function(ABC):
"""
repr_attr = ("id", "name",)
def __hash__(self):
- return hash((self.id, self.type, self.name, self.scope, self.hidden, self.duration,
- self.actual_duration))
+ return self._hash
@staticmethod
def get_data():
@@ -271,7 +275,9 @@ class Function(ABC):
(values, audio cues, next change, data)
Where values is a tuple of (channel, value) elements, audio_cues is a tuple of
- (filename, start time, fade in time, fade out time, fade out start) elements,
+ (filename, aid, start time, fade in time, fade out time, fade out start) elements, aid
+ may be used to uniquely identify instances of audio cues.
+
next_change is the time index of the next lighting change, and data is the state data
(None if unused). values must contain a value for exactly those channels provided in
scope.
@@ -298,11 +304,11 @@ class Function(ABC):
self.actual_duration = min(QLC_INFTY, actual_duration)
self.scope = tuple(scope)
+ self._hash = hash((self.id, self.type, self.name, self.scope, self.hidden, self.duration,
+ self.actual_duration))
+
class FadeFunction(Function):
"""QLC function that can fade in/out."""
- def __hash__(self):
- return hash((super().__hash__(), self.fade_in, self.fade_out))
-
def __init__(self, id_, type_, name, scope, hidden=False, duration=-1, actual_duration=-1, fade_in=0, fade_out=0):
if fade_in >= QLC_INFTY or fade_out >= QLC_INFTY:
raise ValueError("Fades cannot be infinite")
@@ -310,6 +316,15 @@ class FadeFunction(Function):
self.fade_in = min(QLC_INFTY, fade_in)
self.fade_out = min(QLC_INFTY, fade_out)
+ self._hash = hash((self._hash, self.fade_in, self.fade_out))
+
+class Advanceable(ABC):
+ """Function that may be advanced."""
+ @abstractmethod
+ def advance(self, data):
+ """Advance the function."""
+ return
+
## END Base classes
## BEGIN Function classes
@@ -317,26 +332,24 @@ class FadeFunction(Function):
class Audio(FadeFunction):
"""Class for a QLC+ audio function."""
repr_attr = ("id", "fname", "fade_in", "fade_out",)
- def __hash__(self):
- return hash((super().__hash__(), self.fname, self.run_order))
-
def render(self, t, data=None):
"""Render the audio function.
We do not seek to do anything related to audio in this library: the responsibility for
mixing, fading, playing, probing, etc. the audio file is with the specific application.
- As such, this function only returns the relevant data for the audio function.
+ As such, this function only returns the relevant data for the audio function.o
"""
if t > self.duration:
return (), (), -1, data
- return (), ((0, self.fname, self.fade_in, self.fade_out, self.duration-self.fade_out),), self.duration+1-t, data
+ return (), ((0, self.id, self.fname, self.fade_in, self.fade_out, self.duration-self.fade_out),), self.duration+1-t, data
def __init__(self, id_, name, fname, fade_in, fade_out, length, run_order=SINGLESHOT, hidden=False):
super().__init__(id_, AUDIO, name, (), hidden=hidden, duration=length,
actual_duration=length, fade_in=fade_in, fade_out=fade_out)
self.fname = fname
self.run_order = run_order
+ self._hash = hash((self._hash, self.fname, self.run_order))
class Scene(Function):
"""Class for a QLC Scene.
@@ -346,9 +359,6 @@ class Scene(Function):
Scenes are mostly meaningless on their own in this context, they must be attached to a
chaser/show to do anything.
"""
- def __hash__(self):
- return hash((super().__hash__(), self.values))
-
def render(self, t, data=None):
"""All arguments are unused."""
return self.values, (), QLC_INFTY, None
@@ -356,6 +366,7 @@ class Scene(Function):
def __init__(self, id_, name, values, hidden=False):
super().__init__(id_, SCENE, name, (c for c,v in values), hidden=hidden, duration=-1, actual_duration=-1)
self.values = tuple(values)
+ self._hash = hash((self._hash, self.values))
class ChaserStep(FadeFunction):
"""A single step in a chaser."""
@@ -367,9 +378,6 @@ class ChaserStep(FadeFunction):
self.start_time = start_time
self.end_time = end_time
- def __hash__(self):
- return hash((super().__hash__(), self.hold, self.function))
-
def get_data(self, start_time=0):
return self.ChaserStepData(fd=self.function.get_data(), start_time=start_time, end_time=self.duration)
@@ -405,11 +413,12 @@ class ChaserStep(FadeFunction):
nx = min(nx, data.end_time-t)
nacues = []
- for f, s, fin, fout, fstart in acues:
+ for s, aid, f, fin, fout, fstart in acues:
if fstart + fout > self.fade_out + data.end_time:
fstart = data.end_time - self.fade_out
fout = self.fade_out
- nacues.append((f, s+data.start_time, max(self.fade_in, fin), fout, fstart))
+ nacues.append((s+data.start_time, hash((self.id, data.start_time, aid)),
+ f, max(self.fade_in, fin), fout, fstart))
return (values, mul), tuple(nacues), nx, data
@@ -420,8 +429,9 @@ class ChaserStep(FadeFunction):
self.id = id_
self.hold = hold
self.function = function
+ self._hash = hash((self._hash, self.function, self.hold))
-class Chaser(Function):
+class Chaser(Function, Advanceable):
"""Class for representing a QLC+ Chaser or Sequence.
Since they essentially do the same thing (Chaser being more general), they have only one
@@ -433,9 +443,6 @@ class Chaser(Function):
self.step_data = step_data
self.obey_loop = obey_loop
- def __hash__(self):
- return hash((super().__hash__(), self.steps, self.run_order, self.direction))
-
@staticmethod
def advance(t, data):
"""End the current chaser step.
@@ -594,19 +601,18 @@ class Chaser(Function):
self.steps = tuple(steps)
self.run_order = run_order
self.direction = direction
+ self._hash = hash((self._hash, self.steps, self.run_order, self.direction))
class ShowFunction(Function):
"""Class for representing a function in a show."""
repr_attr = ("id", "name", "start_time", ("function", lambda f: f.id))
- def __hash__(self):
- return hash((super().__hash__(), self.function, self.start_time))
-
def render(self, t, data=None):
if data is None:
data = self.function.get_data()
values, acues, nx, data = self.function.render(t-self.start_time, data=data)
- return values, tuple(((at+self.start_time,*others) for at,*others in acues)), nx, data
+ return values, tuple(((at+self.start_time,hash((self.id, self.start_time, aid)),
+ *others) for at,aid,*others in acues)), nx, data
def __init__(self, id_, name, function, start_time):
if function.actual_duration >= QLC_INFTY:
@@ -615,13 +621,11 @@ class ShowFunction(Function):
actual_duration=function.actual_duration)
self.function = function
self.start_time = start_time
+ self._hash = hash((self._hash, self.start_time, self.function))
class ShowTrack(Function):
"""Class for representing a track in a show."""
repr_attr = ("id", "name", ("functions", lambda fs: ','.join(("%d@%d" % (f.function.id, f.start_time) for f in fs))))
- def __hash__(self):
- return hash((super().__hash__(), self.functions))
-
def get_data(self):
return tuple((f.function.get_data() for f in self.functions))
@@ -664,12 +668,10 @@ class ShowTrack(Function):
dur = f.start_time + f.duration
scope.update(f.scope)
super().__init__(id_, "ShowTrack", name, scope, duration=dur, actual_duration=adur)
+ self._hash = hash((self._hash, self.functions))
class Show(Function):
"""Class representing a QLC+ show."""
- def __hash__(self):
- return hash((super().__hash__(), self.tracks))
-
def render(self, t, data=None):
if t > self.actual_duration:
return (), (), -1, data
@@ -768,6 +770,7 @@ class Show(Function):
adur = t.actual_duration
super().__init__(id_, SHOW, name, scope, duration=dur, actual_duration=adur)
self.tracks = tuple(tracks)
+ self._hash = hash((self._hash, self.tracks))
## END Function classes