1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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
"""
|