summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md60
-rw-r--r--blc/render.py8
-rwxr-xr-xblc/workspace.py3
3 files changed, 68 insertions, 3 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3e44d8d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,60 @@
+# BLC
+
+## About
+
+BLC is a program that reads and parses [QLC+](https://www.qlcplus.org/) workspaces and allows
+for the rendering of the contained functions. The goal is to bypass QLC's rather sketchy and
+inconsistent rendering system, while still using QLC+ to actually edit workspaces.
+
+## Modules and Requirements
+
+BLC itself requires a modern Python 3. "Modern" means "as new as you can get" in most cases. I
+don't guarantee the success of anything less that 3.7; I believe that 3.3/3.5 is the minimum
+that you can get away with but they haven't been tested.
+
+The `workspace` module contains the core classes and functions of BLC and handles both loading
+of workspaces and rendering functions. It has no special requirements.
+
+The `audio` module defines the `AudioPlayer` interface for playing audio and defines a couple of
+concrete implementations. The `FFPlayer` class requires [ffmpeg](https://ffmpeg.org/) and the
+`MPVPlayer` class requires [mpv](https://mpv.io/) along with the Python module
+[python-mpv](https://github.com/jaseg/python-mpv). Note that the dependencies for the concrete
+classes are unnecessary if you don't intend to use them.
+
+The `image` module defines some useful functions for visualizing the lighting output of
+functions by turning them into images. This was primarily used in testing BLC's rendering
+functionality, but looks cool regardless. It requires PIL (tested on
+[pillow](https://python-pillow.org/)) for the actual images and [ffmpeg](https://ffmpeg.org/)
+for reading audio files.
+
+The `output` module defines the `LightingOutput` interface for sending light cues.
+
+The `ola` module defines the `OLAOutput` implementation of `LightingOutput`, which sends cues
+using [OLA](https://www.openlighting.org/ola/)'s Python bindings (usually built with OLA). I
+highly recommend using this output; it moves the responsibility of handling the actual output
+devices to better-tested code (OLA). It should also allow QLC+ and BLC to operate simultaneously
+on the same output (untested).
+
+The `render` module defines (primarily) the `BasicRenderer` class, which should be sufficient to
+serve as the actual live renderer for a user interface. This supports the rendering of
+"toplevel" functions, which are Chasers and Shows.
+
+The `tk` module defines a couple of useful Tk widgets for dealing with BLC. It requires `tk` to
+be installed on your system (usually included on Windows, try `python3 -c 'import tkinter'` to
+check).
+
+## Workspace Architecture
+
+BLC attempts to maintain the same structure as QLC+ wherever possible. The architecture of
+a workspace is split into two parts: topological and functional.
+
+The topological part represents the "physical" layout of the workspace: the fixtures, the
+channels, and the universes. `Channel`s are assigned to `Fixture`s and `Fixture`s are assigned
+to `Universe`s, as in QLC+.
+
+The functional part encompasses the actual lighting functions. BLC defines a base `Function`
+class, which defines a `render` method that renders the output at a given time. BLC implements
+most of the QLC+ functions (the ones used by incandescent lighting anyways): `Scene`, `Chaser`,
+`Show`, and `Audio`. QLC+ sequences are implemented as `Chaser`s in BLC; I have no idea why this
+is not the case in QLC+. Composite functions like `Chaser` and `Show` delegate the actual
+rendering to their sub-functions, demuxing the output and fading as necessary.
diff --git a/blc/render.py b/blc/render.py
index d87e8b6..7dbeee2 100644
--- a/blc/render.py
+++ b/blc/render.py
@@ -9,6 +9,7 @@ from .output import LightingOutput
from .workspace import SHOW, CHASER, Advanceable, QLC_INFTY
class FunctionQueue:
+ """Queue for executing functions in sequence."""
def after(self, t: float, f: callable):
"""Run the given function after t milliseconds."""
self.queue.put((time.monotonic() + t/1000, f))
@@ -23,7 +24,7 @@ class FunctionQueue:
def __init__(self):
self.queue = queue.SimpleQueue()
-class Renderer:
+class BasicRenderer:
"""Basic renderer for functions.
Supports live-rendering Chasers and Shows.
@@ -93,6 +94,11 @@ class Renderer:
self.stall_lock.acquire(blocking=False)
self.stall_lock.release()
+ @property
+ def current_time(self):
+ """Return the current time in the function."""
+ return (time.monotonic() - self.start_time) if self.start_time is not None else -1
+
def __init__(self, f, lo:LightingOutput, ap: AudioPlayer=DefaultAudioPlayer, minnx=-1):
if f.type not in (SHOW, CHASER):
raise ValueError("Only Shows and Chasers may be used as toplevel functions")
diff --git a/blc/workspace.py b/blc/workspace.py
index c9f9a35..03141e4 100755
--- a/blc/workspace.py
+++ b/blc/workspace.py
@@ -104,8 +104,7 @@ import json
from multiprocessing.pool import ThreadPool
import subprocess as subp
import logging
-
-from lxml import etree
+import xml.etree.ElementTree as etree
## BEGIN Constants