summaryrefslogtreecommitdiff
path: root/functions/function.py
blob: 97c98f7ac550cf1347b75d78b54d914167439650 (plain)
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
        """