summaryrefslogtreecommitdiff
path: root/functions
diff options
context:
space:
mode:
authorBen Connors <benconnors@outlook.com>2019-09-25 23:05:14 -0400
committerBen Connors <benconnors@outlook.com>2019-09-25 23:05:14 -0400
commit2b8a53f98c44e6e78d49b7c246731deef75ed6d3 (patch)
treed85e383c0d54d662ea93384b177a0ad59a40917e /functions
parent7f85bd8ed84b23fc4e683ab90fc7babe288f1a27 (diff)
Change module layout; start chaser work
- Fix up callbacks - Clean up function implementations - More properties to prevent editing of attributes - Start work on chasers - Implement framework for chaser steps
Diffstat (limited to 'functions')
-rw-r--r--functions/__init__.py0
-rw-r--r--functions/audio.py118
-rw-r--r--functions/function.py88
-rw-r--r--functions/scene.py144
4 files changed, 0 insertions, 350 deletions
diff --git a/functions/__init__.py b/functions/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/functions/__init__.py
+++ /dev/null
diff --git a/functions/audio.py b/functions/audio.py
deleted file mode 100644
index d0e599d..0000000
--- a/functions/audio.py
+++ /dev/null
@@ -1,118 +0,0 @@
-"""Audio function module.
-
-Contains the definition of the Audio, the audio primitive.
-"""
-
-import xml.etree.ElementTree as et
-
-from .function import Function
-
-from ..constants import AUDIO, BXW
-from ..exceptions import LoadError
-
-class Audio(Function):
- """Class representing an audio cue.
-
- This is the primitive for audio, and all sound cues must be based in some manner off of
- Audio. This function merely plays a single audio file once, starting at t=0.
-
- The duration of the audio is automatically determined and is zero if the file does not
- exist or is unsupported by ffmpeg.
- """
- scope = ()
- audio_scope = frozenset()
- actual_duration = 0
-
- def __init__(self, w, id_ = None, name = None, fade_in = 0, fade_out = 0,
- filename: str = None):
- super().__init__(w=w, type_=AUDIO, id_=id_, name=name, duration=0,
- fade_in=fade_in, fade_out=fade_out)
-
- self._filename = filename
-
- if filename is not None:
- self.duration = self.w.get_audio_length(filename)
- self.audio_scope = frozenset(((filename,),))
- else:
- self.duration = 0
- self.audio_scope = frozenset()
-
- self.actual_duration = self.duration
-
- def get_data(self):
- return None
-
- def copy_data(self, data):
- return None
-
- def render(self, t, data = None):
- return ((),
- ((self.id, self._filename, 0, self.fade_in, self.duration, self.fade_out),),
- None)
-
- @property
- def filename(self):
- """Return the current audio filename."""
- return self._filename
-
- @filename.setter
- def filename(self, value: str):
- """Set the current audio filename, updating the duration."""
- if value is not None:
- self.duration = self.w.get_audio_length(value)
- self.audio_scope = frozenset(((value,),))
- else:
- self.duration = 0
- self.audio_scope = frozenset()
-
- self._filename = value
- self.w.function_changed(self)
-
- def serialize(self) -> et.Element:
- e = et.Element(BXW+"function")
- e.set("type", self.type)
- e.set("id", str(self.id))
- e.set("name", self.name)
- e.set("fade-in", str(self.fade_in))
- e.set("fade-out", str(self.fade_out))
- if self.filename is not None:
- filename = et.SubElement(e, BXW+"filename")
- filename.text = self.filename
-
- return e
-
- @classmethod
- def deserialize(cls, w, e):
- if e.tag != BXW+"function":
- raise LoadError("Invalid function tag")
- elif e.get("type") != AUDIO:
- raise LoadError("Load delegated to wrong class (this is a bug)")
-
- id_ = cls.int_or_none(e.get("id"))
- if id_ is None:
- raise LoadError("Function tag has invalid/missing ID")
-
- name = e.get("name")
-
- fade_in = e.get("fade-in")
- try:
- fade_in = int(fade_in) if fade_in else 0
- except ValueError:
- raise LoadError("Invalid fade in")
-
- fade_out = e.get("fade-out")
- try:
- fade_out = int(fade_out) if fade_out else 0
- except ValueError:
- raise LoadError("Invalid fade out")
-
- if len(e) > 1:
- raise LoadError("Audio tag can have at most one filename")
- elif len(e) == 1:
- filename, = e
- filename = filename.text
- else:
- filename = None
-
- return cls(w=w, id_=id_, name=name, filename=filename, fade_in=fade_in,
- fade_out=fade_out)
diff --git a/functions/function.py b/functions/function.py
deleted file mode 100644
index 97c98f7..0000000
--- a/functions/function.py
+++ /dev/null
@@ -1,88 +0,0 @@
-"""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
- """
diff --git a/functions/scene.py b/functions/scene.py
deleted file mode 100644
index 958138f..0000000
--- a/functions/scene.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""Scene function module.
-
-Contains the definition of the Scene, the lighting primitive.
-"""
-
-import xml.etree.ElementTree as et
-from typing import Mapping
-
-from .function import Function
-from ..topology import Fixture
-from ..constants import INFTY, SCENE, BXW
-from ..exceptions import LoadError
-
-class Scene(Function):
- """Class representing a lighting scene.
-
- This is the primitive for lighting, and all light cues must be based in some manner off
- of Scene. Scenes are simple, having no fading and infinite duration.
-
- Modifying the scene can be done in a few ways: the easiest is using the dictionary-
- style accesses, e.g. setting the values using:
-
- scene[channel] = value
-
- Values may be removed from the scene using the ``del`` operator and retrieved in the
- same fashion. Alternatively, values can be updated in bulk using ``Scene.update`` or
- set in bulk with ``Scene.set``.
- """
- audio_scope = ()
- scope = frozenset()
- actual_duration = INFTY
- values = frozenset()
-
- def __init__(self, w, id_ = None, name = None, values: Mapping[Fixture.Channel, int] = None):
- super().__init__(w=w, type_=SCENE, id_=id_, name=name, duration=INFTY, fade_in=0,
- fade_out=0)
- self._values = {}
- self._render = ()
-
- self._update(values, changed=False)
-
- def _update_render(self, changed=True):
- self._render = tuple(self._values.items())
- self.scope = frozenset(self._values.keys())
- self.values = self._render
-
- if changed:
- self.w.function_changed(self)
-
- def set(self, v):
- """Set the scene's values to the given ones.
-
- Equivalent to update, except that all non-given values are deleted from the scene.
- """
- self._values = {}
- self.update(v)
-
- def _update(self, v, changed=True):
- if isinstance(v, dict):
- 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)")
- vn.append((c, val))
- for c, val in vn:
- self._values[c] = val
- self._update_render(changed=changed)
-
- def update(self, v):
- """Update the scene's values."""
- self._update(v=v, changed=True)
-
- def __getitem__(self, c: Fixture.Channel):
- return self._values[c] if c in self._values else None
-
- def __setitem__(self, c: Fixture.Channel, v: int):
- self.update(((c, v),))
-
- def __delitem__(self, c: Fixture.Channel):
- if c in self._values:
- del self._values[c]
- self._update_render()
-
- def get_data(self):
- return None
-
- def copy_data(self, data):
- return None
-
- def render(self, t, data = None):
- return (self._render, (), None)
-
- def serialize(self):
- e = et.Element(BXW+"function")
- e.set("type", self.type)
- e.set("id", str(self.id))
- e.set("name", self.name)
- for c, v in self.values:
- ce = et.SubElement(e, BXW+"value")
- ce.set("fixture", str(c.f.id))
- ce.set("channel", str(c.id))
- ce.text = str(v)
-
- return e
-
- @classmethod
- def deserialize(cls, w, e):
- if e.tag != BXW+"function":
- raise LoadError("Invalid function tag")
- elif e.get("type") != SCENE:
- raise LoadError("Load delegated to wrong class (this is a bug)")
-
- id_ = cls.int_or_none(e.get("id"))
- if id_ is None:
- raise LoadError("Function tag has invalid/missing ID")
-
- name = e.get("name")
-
- values = {}
- for ve in e:
- if ve.tag != BXW+"value":
- raise LoadError("Invalid value tag")
-
- fixture = cls.int_or_none(ve.get("fixture"))
- channel = cls.int_or_none(ve.get("channel"))
-
- if None in (fixture, channel):
- raise LoadError("Missing/invalid fixture/channel value")
- elif fixture not in w.fixtures:
- raise LoadError("Missing fixture ID %d" % fixture)
- elif channel >= len(w.fixtures[fixture].channels):
- raise LoadError("Fixture %d missing channel ID %d" % (fixture, channel))
-
- channel = w.fixtures[fixture].channels[channel]
-
- value = cls.int_or_none(ve.text)
- if value is None:
- raise LoadError("Missing/invalid value for channel")
-
- values[channel] = value
-
- return cls(w=w, id_=id_, name=name, values=values)