summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--blc2/__init__.py2
-rw-r--r--blc2/functions/audio.py12
-rw-r--r--blc2/functions/chaser.py36
-rw-r--r--blc2/functions/function.py8
-rw-r--r--blc2/functions/scene.py13
-rw-r--r--blc2/workspace.py6
6 files changed, 62 insertions, 15 deletions
diff --git a/blc2/__init__.py b/blc2/__init__.py
index 72a362d..24a8009 100644
--- a/blc2/__init__.py
+++ b/blc2/__init__.py
@@ -1,2 +1,4 @@
from .workspace import Workspace
from .topology import Fixture
+
+__version__ = "v0.0.1"
diff --git a/blc2/functions/audio.py b/blc2/functions/audio.py
index c30f279..5881e18 100644
--- a/blc2/functions/audio.py
+++ b/blc2/functions/audio.py
@@ -49,7 +49,10 @@ class Audio(Function):
return self._fade_in
@fade_in.setter
- def fade_in(self, v):
+ def fade_in(self, v: int):
+ if not isinstance(v, int):
+ v = int(v)
+
if v < 0:
raise ValueError("Fades must be nonnegative")
@@ -63,6 +66,9 @@ class Audio(Function):
@fade_out.setter
def fade_out(self, v):
+ if not isinstance(v, int):
+ v = int(v)
+
if v < 0:
raise ValueError("Fades must be nonnegative")
@@ -87,7 +93,7 @@ class Audio(Function):
def duration(self):
return self._duration
- def _set_duration(self, value, update=True):
+ def _set_duration(self, value: int, update=True):
"""Set the duration.
This is called by Workspace when the duration for the file has been rechecked; do
@@ -105,7 +111,7 @@ class Audio(Function):
def copy_data(self, data):
return None
- def render(self, t, data = None):
+ def render(self, t: int, data = None):
if t > self._actual_duration:
return (), (), None
return ((),
diff --git a/blc2/functions/chaser.py b/blc2/functions/chaser.py
index a9e51a7..ea37ca0 100644
--- a/blc2/functions/chaser.py
+++ b/blc2/functions/chaser.py
@@ -8,7 +8,13 @@ from .function import Function
from ..exceptions import LoadError
class Chaser(Function):
- """Class for chasers."""
+ """Class for chasers.
+
+ Chaser data theoretically supports re-use after a step in the Chaser has been deleted;
+ however, it is recommended to obtain a new copy of the data starting at the desired
+ step after steps have been moved or deleted (or even changed, in the case of timing
+ changes).
+ """
type = CHASER
def __init__(self, w, id_ = None, name = None, advance_mode = ONESHOT):
@@ -35,6 +41,9 @@ class Chaser(Function):
@advance_mode.setter
def advance_mode(self, v):
+ if v not in (ONESHOT, LOOP, RANDOM):
+ raise ValueError("Invalid advance mode")
+
if v != self._advance_mode:
self._advance_mode = v
self._recalculate()
@@ -47,7 +56,7 @@ class Chaser(Function):
def fade_out(self):
return 0
- def get_data(self, start_at=None): #pylint: disable=arguments-differ
+ def get_data(self, start_at: int = None): #pylint: disable=arguments-differ
data = self.ChaserData(self)
if start_at is not None:
data = self.advance(0, data, n=start_at)
@@ -128,7 +137,10 @@ class Chaser(Function):
s.index._set_index(i) #pylint: disable=protected-access
def register_step(self, step):
- """Register a new step."""
+ """Register a new step.
+
+ This should only be called by ChaserStep's constructor.
+ """
if step.index == -1:
step._index = len(self._steps) #pylint: disable=protected-access
elif step.index is None:
@@ -142,8 +154,16 @@ class Chaser(Function):
self._fix_indices()
self._recalculate()
- def move_step(self, step, position):
- """Move a step around."""
+ def move_step(self, step, position: int):
+ """Move a step around.
+
+ This should be done by changing the ChaserStep's index.
+
+ A position of -1 or a value greater than the length of the chaser is interpreted as
+ "make this step last".
+ """
+ if not isinstance(position, int):
+ position = int(position)
if isinstance(step, int):
step = self._steps[step]
elif step not in self._steps:
@@ -171,11 +191,15 @@ class Chaser(Function):
step.delete()
def delete(self):
+ """Delete the Chaser.
+
+ Also deletes all steps.
+ """
for step in self._steps:
self.delete_step(step)
super().delete()
- def advance(self, t, data, n=None):
+ def advance(self, t: int, data, n=None):
"""Advance the chaser at the given time.
If ``n`` is ``None``, the chaser is advanced one step. Otherwise, it is advanced to
diff --git a/blc2/functions/function.py b/blc2/functions/function.py
index 21632a7..6a5d92a 100644
--- a/blc2/functions/function.py
+++ b/blc2/functions/function.py
@@ -25,14 +25,18 @@ class Function(XMLSerializable, metaclass=ABCMeta):
It is an error to change Function.type or Function.fade_out_mode in user code. These
values are public for informational purposes.
+
+ Type checking and implicit conversion is done wherever possible on functions that
+ change state (e.g. setting the fades, setting values on a Scene). However, no type
+ checking or conversion is done on read-only or rendering functions to save time.
"""
type = FUNCTION
fade_out_mode = EXTERNAL
def __init__(self, w: "Workspace", id_: int = None, name: str = None):
self.w = w
- self._id = id_ if id_ is not None else w.next_function_id
- self._name = name if name else "%s %s" % (self.type, self.id)
+ self._id = int(id_) if id_ is not None else w.next_function_id
+ self._name = str(name) if name else "%s %s" % (self.type, self.id)
self.w.register_function(self)
diff --git a/blc2/functions/scene.py b/blc2/functions/scene.py
index 239df3c..b694b8b 100644
--- a/blc2/functions/scene.py
+++ b/blc2/functions/scene.py
@@ -89,12 +89,17 @@ class Scene(Function):
v = v.items()
vn = []
for c, val in v:
- val = int(val)
- if val < 0 or val > 255:
- raise ValueError("Values must be integers on [0,256)")
+ if val is not None:
+ val = int(val)
+ if val < 0 or val > 255:
+ raise ValueError("Values must be integers on [0,256)")
vn.append((c, val))
for c, val in vn:
- self._values[c] = val
+ if val is None:
+ if c in self._values:
+ del self._values[c]
+ else:
+ self._values[c] = val
self._update_render(changed=changed)
def update(self, v):
diff --git a/blc2/workspace.py b/blc2/workspace.py
index 89bda91..5ec5247 100644
--- a/blc2/workspace.py
+++ b/blc2/workspace.py
@@ -35,6 +35,12 @@ class Workspace(XMLSerializable):
Note that all callbacks are executed synchronously: if they require long periods of
time to execute, the callback should handle scheduling the actual work on a different
thread.
+
+ This library is designed with two user interface "modes" in mind: "run" and "edit". All
+ functions that edit the Workspace's internal state are considered to be in "edit" mode
+ and efficiency is less important, as it is not designed with real-time playback in
+ mind. The read-only functions, and especially `render` are designed with real-time
+ playback in mind. As such, no editing should be allowed while in "run" mode.
"""
def __init__(self, name: str, author: str, version: int, modified: dt.datetime):
self.name = name