diff options
Diffstat (limited to 'functions/function.py')
-rw-r--r-- | functions/function.py | 88 |
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 + """ |