summaryrefslogtreecommitdiff
path: root/functions/function.py
diff options
context:
space:
mode:
Diffstat (limited to 'functions/function.py')
-rw-r--r--functions/function.py88
1 files changed, 88 insertions, 0 deletions
diff --git a/functions/function.py b/functions/function.py
new file mode 100644
index 0000000..97c98f7
--- /dev/null
+++ b/functions/function.py
@@ -0,0 +1,88 @@
+"""Base function module.
+
+Contains the generic Function interface.
+"""
+
+from abc import ABCMeta, abstractmethod, abstractproperty
+from typing import Set, Any
+
+from ..topology import Fixture
+from ..constants import INFTY
+from ..interfaces import XMLSerializable
+
+class Function(XMLSerializable, metaclass=ABCMeta):
+ """Class representing a generic function.
+
+ Many of the properties here should not be implemented as properties for performance
+ reasons.
+
+ Functions and properties required for rendering, e.g. ``Function.render`` and
+ ``Function.actual_duration``, should be written to be performant.
+
+ Any change to the scope, audio scope, or actual duration of the function must be
+ reported using ``Workspace.function_changed`` with the new values. This should also be
+ extended to any change in e.g. values for lighting and audio primitives.
+ """
+ def __init__(self, w: "Workspace", type_: str, id_: int = None, name: str = None,
+ duration: int = INFTY, fade_in: int = 0, fade_out: int = 0):
+ self.w = w
+ self.id = id_ if id_ is not None else w.next_function_id
+ self.name = name if name else "%s %s" % (type_, self.id)
+ self.type = type_
+ self.duration = duration
+ self.fade_in = fade_in
+ self.fade_out = fade_out
+
+ self.w.register_function(self)
+
+ def delete(self):
+ """Delete the function from the Workspace."""
+ self.w.function_deleted(self)
+
+ @abstractproperty
+ def scope(self) -> Set[Fixture.Channel]:
+ """Return the set of channels affected by this function."""
+
+ @abstractproperty
+ def audio_scope(self) -> Set[str]:
+ """Return the set of audio filenames that may be used by this function."""
+
+ @abstractmethod
+ def get_data(self) -> Any:
+ """Return the default data for this function."""
+
+ @abstractmethod
+ def copy_data(self, data: Any) -> Any:
+ """Duplicate the given data."""
+
+ @abstractproperty
+ def actual_duration(self):
+ """Return the actual duration of the function, including all fades."""
+
+ @abstractmethod
+ def render(self, t: int, data: Any = None):
+ """Render the function at the given time.
+
+ This function must return a 3-tuple:
+
+ (light_cues, audio_cues, new_data)
+
+ Where ``light_cues`` is a iterable of (channel, value) pairs. It is an error for
+ ``light_cues`` to contain channels other than exactly this Function's scope.
+ ``audio_cues`` is an iterable of audio cues of the form:
+
+ (guid, filename, start_time, fade_in, end_time, fade_out)
+
+ The ``guid`` is globally unique: that is, it is unique to that specific audio cue,
+ even if the same file is played multiple times or in different places. Note that
+ ``end_time`` may be ``INFTY``, in which case the file should be played to its end.
+ In the case of infinite-duration chasers, the ``end_time`` may change over
+ subsequent calls to this function.
+
+ Note that ``data`` is not necessarily mutable: ``render`` should be called like:
+
+ lights, sound, data = f.render(t, data)
+
+ :param t: the time to render at, in milliseconds
+ :param data: the function data to use
+ """