An
environment supporting
live coding for the
creation of algorithmic light shows in Clojure,
leveraging the
Open Lighting Architecture with
the help of
ola-clojure,
wayang,
beat-link, and pieces of
the Overtone toolkit. Beyond building on
pieces of Overtone, the entire Afterglow project was
inspired by it.
This page provides an introduction in how to install and use Afterglow. The Developer Guide goes much deeper, and there is also API documentation. For more interactive help, the Afterglow stream on Zulip is the place to start, and if you want to see (or contribute to) more structured and lasting community-driven documentation, there is also a project wiki.
tl;dr—show me? Check out the Show Control pics and performance video.
As suggested by the live-coding orientation mentioned above, which is designed to let you inject your own code right into the frame rendering process, Afterglow takes a very different approach to controlling light shows than other software. It won’t be right for everyone, but will be extremely compelling to a particular niche. The early stages of its rendering loop can offer higher levels of abstraction than the usual DMX channel value or fixture function (although those are fully supported too):
You can express your desired results in terms of an abstract color, including support for the hue-saturation-lightness model, which is great for algorithmic looks, and have it translated to whatever color channels (or color wheel) your fixture supports.
Groups of moving heads can be told to face particular directions by specifying parameterized vectors, or to aim at a particular point in space, and Afterglow figures out how to translate that into DMX control values given its understanding of the fixture and where, and at what angle, you hung it.
There are a variety of oscillators which can efficiently drive effect parameters.
You can also create complex effects, with adjustable parameters that can be controlled through a rich binding to an Ableton Push or Novation Launchpad family controller, or via Open Sound Control (OSC)—even wirelessly from a tablet or smartphone.
The timing of effects is pervasively influenced by a deep notion of musical time, with support for synchronization via MIDI clock, Traktor Beat Phase, or Pioneer Pro DJ Link beat grids.
You can even host Afterglow within Cycling ‘74’s Max visual interactive environment.
If any of this sounds interesting to you, read on to see how to get started!
Install OLA.
(On the Mac I recommend using Homebrew which lets you simply
brew install ola
). Once you launch the olad
server you can
interact with its embedded
web server, which is very helpful
in seeing whether anything is working; you can even watch live DMX
values changing.
:wrench: If you are installing Afterglow on Windows, see the Wiki discussion about OLA options.
For now set up a Clojure project using Leiningen.
:wrench: If you were using older releases of Afterglow and installed CoreMIDI4J in
/Library/Java/Extensions
, you need to remove it, because Afterglow now embeds an improved version and uses it when necessary.
If you want to run Afterglow as a standalone executable, you can download the executable überjar from the releases page.
For an example of a project which uses Afterglow as a dependency, as described above, see afterglow-max, which hosts Afterglow inside Cycling ‘74’s Max.
Although Afterglow is far from finished, it’s ready for the world to start exploring, and helping decide directions in which to grow next (as well as identifying areas where the documentation needs clarification or reinforcement).
Most of the crazy ideas have panned out and been implemented, and I am fleshing out the basic details needed for everyday use. The examples are starting to be intriguing and informative, and the Developer Guide is getting substantial. The modeling of fixtures, channels, etc. is coming together nicely, though there may be a few more changes.
There is now an embedded web application, which is growing into a show control interface for people who are not Clojure hackers, and a useful adjunct to the Ableton Push and Launchpad family control surface interfaces. Each is explained in the documentation link above. Afterglow also includes the beginnings of a show visualizer for designing and working on effects without having to physically hook up lights (a proof of concept, really, at this point). This is implemented in WebGL using a volumetric ray tracer and looks quite promising, at least for a small number of fixtures; it will probably overwhelm the graphics processor on most systems once you add too many lights. However, the framework can be used by someone who actually knows OpenGL programming to build a more scalable preview (albeit one that probably doesn’t look quite so photo-realistic with beams impacting drifting fog). This is an area where I would love some help if it sounds interesting!
Deep Symmetry’s projects are generously sponsored with hosting by Zulip, an open-source modern team chat app designed to keep both live and asynchronous conversations organized. Thanks to them, you can chat with our community, ask questions, get inspiration, and share your own ideas.
The rest of this document primarily provides an introduction to the configuration of Afterglow from the command line and text files. The show control interface is explained in the web and Push sections.
Although you will often want to use Afterglow from a Clojure repl, you
can also bring it up as an executable jar, and run it using java -jar
with command-line arguments:
> java -jar afterglow.jar --help
afterglow 0.2.4, a live-coding environment for light shows.
Usage: afterglow [options] [init-file ...]
Any init-files specified as arguments will be loaded at startup,
in the order they are given, before creating any embedded servers.
Options:
-w, --web-port PORT 16000 Port number for web UI
-n, --no-browser Don't launch web browser
-o, --osc-port PORT 16001 Port number for OSC server
-r, --repl-port PORT Port number for REPL, if desired
-l, --log-file PATH logs/afterglow.log File into which log is written
-H, --olad-host HOST localhost Host name or address of OLA daemon
-P, --olad-port PORT 9010 Port number OLA daemon listens on
-q, --convert-qxf PATH Convert QLC+ fixture file and exit
-h, --help Display help information and exit
If you translate a QLC+ fixture definition file, Afterglow will try to write
its version in the same directory, but won't overwrite an existing file.
If you do not explicitly specify a log file, and Afterglow cannot write to
the default log file path, logging will be silently suppressed.
Please see https://github.com/Deep-Symmetry/afterglow for more information.
As noted, you can pass a list of init-files when you run Afterglow
this way, which gives you the opportunity to set up the actual
universes, fixtures, effects, and cues that you want to use in your
show. As a starting point, you could put something like the following
in a file my-show.clj
and then invoke Afterglow as java -jar afterglow.jar my-show.clj
:
(ns my-show
"Set up the fixtures, effects, and cues I actually want to use."
;; TODO: Your list of required namespaces will differ from this, depending on
;; what fixtures you actually use, and what effects and cues you create.
(:require [afterglow.core :as core]
[afterglow.transform :as tf]
[afterglow.effects.color :refer [color-effect]]
[afterglow.effects.cues :as cues]
[afterglow.effects.dimmer :refer [dimmer-effect]]
[afterglow.effects.fun :as fun]
[afterglow.effects.movement :as move]
[afterglow.effects.oscillators :as oscillators]
[afterglow.effects.params :as params]
[afterglow.fixtures.blizzard :as blizzard]
[afterglow.rhythm :as rhythm]
[afterglow.show :as show]
[afterglow.show-context :refer :all]
[com.evocomputing.colors :refer [create-color hue adjust-hue]]
[taoensso.timbre :as timbre]))
(defonce ^{:doc "Holds my show if it has been created,
so it can be unregistered if it is being re-created."}
my-show
(atom nil))
(defn use-my-show
"Set up the show on the OLA universes it actually needs."
[]
;; Create, or re-create the show. Make it the default show so we don't
;; need to wrap everything below in a (with-show sample-show ...) binding.
(set-default-show!
(swap! my-show (fn [s]
(when s
(show/unregister-show s)
(with-show s (show/stop!)))
;; TODO: Edit this to list the actual OLA universe(s) that
;; your show needs to use if they are different than
;; just universe 1, as below, and change the description
;; to something descriptive and in your own style:
(show/show :universes [1] :description "My Show"))))
;; TODO: Replace this to patch in an actual fixture in your show, at its actual
;; universe, DMX address, physical location and orientation, then add all
;; your other fixtures one by one.
(show/patch-fixture! :torrent-1 (blizzard/torrent-f3) 1 1
:x (tf/inches 44) :y (tf/inches 51.75) :z (tf/inches -4.75)
:y-rotation (tf/degrees 0))
;; Return the show's symbol, rather than the actual map, which gets huge with
;; all the expanded, patched fixtures in it.
'*show*)
(core/init-logging) ; Log at :info level to rotating files in logs/ subdirectory.
(use-my-show) ; Set up my show as the default show, using the function above.
;; TODO: Add your custom effects, then assign them to cues with sensible colors
;; See afterglow.examples for examples.
As noted, you will want to look at the
afterglow.examples
namespace for some examples of how to populate this file; the rest of
this section gives an overview and walk-through of how pieces of that
namespace work. The :require
section at the top of my-show.clj
is
set up to make it easy to cut and paste from these examples, although
it is not complete, and you will eventually need to learn how to
adjust and optimize it yourself.
The example code above configures Afterglow to log to a set of rotating log files in a
logs/
subdirectory of your project. Afterglow will attempt to create that directory if it does not exist. If you want to see any logging information, which can be quite useful when troubleshooting, you will need to ensure that the path to the logs directory is writeable (or that the logs directory exists and is writable), otherwise the logging mechanism will silently do nothing. The logs will stay out of your way until you are interested in them, and take up a limited amount of space, but whenever you do want to watch what Afterglow is doing, you can look at them, ortail -f logs/afterglow.log
to watch it live.
As your show gets more complex, you may want to split this into
multiple files, which you can either load by listing them all on the
command line, or by using Clojure’s load-file
function from within
the first file. Or, once you are comfortable with idomatic Clojure
development, by organizing them into a hierarchy of namespaces, and
using the normal :require
mechanism that is used to pull in
Afterglow’s own namespaces.
:heavy_exclamation_mark: At this early stage of development, using Afterglow as an executable jar is less-tested territory, and you may find surprising bugs... though this is becoming less of an issue since the advent of afterglow-max, which is putting Afterglow through its paces as an embedded jar. In any case, although the project will gradually evolve into a system that non-Clojure hackers can use, for now you are probably best off playing with it inside a Clojure development environment, or within Max, likely with a Clojure environment connected via nREPL.
Assuming you are using it from within a REPL, there is a namespace
afterglow.examples
which is intended to help you get started quickly
in exploring the environment, as well as serving as an example of how
to configure your own shows, fixtures, effects, and cues.
The next two lines are not needed if you are using a checkout of the Afterglow source code rather than the library version described above, since the project is configured to start you in this namespace for convenience.
(require 'afterglow.examples)
(in-ns 'afterglow.examples)
When you run Afterglow as an executable jar, it will automatically open a web browser window on its embedded web interface. If you are using it in another way, you can bring up the web interface, and open a browser window on it, with a one-liner like this (the first argument specifies the port on which to run the web interface, and the second controls whether a browser window should be automatically opened):
(core/start-web-server 16000 true)
As noted at the bottom, the web interface provides a minimal console as well, so if you are running Afterglow from a jar and just want to tweak something quickly, you can use that:
However, this does not offer the valuable support you would have from a dedicated REPL like Cider (in Emacs) or Cursive (in IntelliJ): things like symbol completion, popup documentation, and command-line recall, which make for a vastly more productive exploration session. So even when you are running from a jar rather than launching from a REPL, you will often want to access a real REPL. You can accomplish that with command-line arguments or by using the web console to invoke core/start-nrepl and then connecting your favorite REPL environment to the network REPL port you created.
The web interface does provide a nice show control page, though, with access to a scrollable grid of cues, and the ability to track the cues displayed on a physical cue grid control surface like the Ableton Push or a current member of the Novation Launchpad family, so you can control them from either place, and see the names that go with the colored buttons on the control surface. This animated GIF shows how cues respond to clicks, lightening while they run, and darkening any cues which cannot run at the same time. It also shows how you can scroll around a larger grid than fits on the screen at one time (although it has reduced colors, frame rate, and quality when compared to the actual web interface):
Here is the Ableton Push interface tied to the same cue grid. This physical control surface lets you trigger more than one cue at the same time, and also gives you niceties unavailable with a mouse, like pressure sensitivity so your effect intensity, speed, color, or other parameters can be varied as you alter the pressure which you are applying to the pads:
You can adjust running effects, scroll around the cue grid, and adjust or sync the show metronome from either interface. Other MIDI controllers can be mapped to provide similar functionality, and hopefully such mappings will make their way into Afterglow soon (indeed, the current Novation Launchpad family is now supported too). The Afterglow mappings are done entirely on the User layer as well, so they coexist gracefully with Ableton Live, and you can switch back and forth by pressing the User button if you want to perform with both.
But, getting back to our REPL-based example: We next start the sample show, which runs on DMX universe 1. You will want to have OLA configured to at least have an dummy universe with that ID so you can watch the DMX values using its web interface. It would be even better if you had an actual DMX interface hooked up, and changed the show to include some real lights you have connected. Either way, here is how you start the show sending control signals to lights:
(use-sample-show) ; Create the sample show that uses universe 1.
(show/start!) ; Start sending its DMX frames.
The afterglow.examples
namespace includes a helper function,
fiat-lux
, to assign a nice cool blue color to all lights in the
sample show, set their dimmers to full, and open the shutters of the
Torrent moving-head spots, which can be called like this:
(fiat-lux)
So if you happened to have the same fixtures hooked up, assigned the
same DMX addresses as I did when I wrote this, you would see a bunch
of blue light. More realistically, you can navigate to the olad
embedded web server and see the
non-zero DMX values in the blue and dimmer channels, assuming you have
set up a Universe with ID 1.
In an environment where you are running multiple shows, the more general way of working with one would look like:
(def another-show (some-function-that-creates-a-show))
(with-show another-show
(show/start!)
(fiat-lux))
However, the
examples
namespace assumes you are just using one, and has set it up as the default show, like this:
(set-default-show! sample-show)
That saves us the trouble of wrapping all our show manipulation functions inside of
(with-show ...)
to establish a context. You will likely want to do something similar in setting up your own shows, since a single show is the most common scenario. See theafterglow.show-context
API documentation for more details. Theshow-context
namespace also defines the dynamic variable*show*
which you can use to refer to the current default show when you need to mention it explicitly, as you will see in some of the examples below.
The actual content of fiat-lux
is quite simple, creating
three effects to achieve the goals mentioned above:
(defn fiat-lux
"Start simple with a cool blue color from all the lights."
[]
(show/add-effect! :color (global-color-effect "slateblue"
:include-color-wheels? true))
(show/add-effect! :dimmers (global-dimmer-effect 255))
(show/add-effect! :torrent-shutter
(afterglow.effects.channel/function-effect
"Torrents Open" :shutter-open 50
(show/fixtures-named "torrent"))))
We can make the lights a little dimmer...
(show/add-effect! :dimmers (global-dimmer-effect 200))
Adding a function with the same keyword as an existing function replaces the old one. The dimmer channels drop from 255 to 200.
But for dimmer channels, there is an even better way of doing that:
(master-set-level (:grand-master *show*) 80)
All cues which set dimmer levels are tied to a dimmer master chain. If none is specified when creating the cue, they are tied directly to the show’s dimmer grand master. Setting this to a value less than 100 scales the dimmer values sent to the lights down by that amount. So the above command dims the lights to 80% of their possible brightness, no matter what else the cues are trying to do. See the dimmer effects API documentation for more details. Here is an example of what I call right away when testing effects in my office with the little Korg nanoKONTROL 2 plugged in:
(show/add-midi-control-to-master-mapping "slider" 0 7)
And then the last fader acts as my grand master dimmer, and I can quickly get relief from overly bright lights. (In a real performance context, you would want to use this alternate approach to automatically set up your bindings whenever the controller is connected. That way, if someone trips over the controller cable, as soon as you plug it back in, you are good to go again.)
If you have an Ableton Push, it is even easier to have intutive control over your show’s grand master dimmer. As soon as you bind the Push to your show, the Push Master encoder is automatically tied to the show master dimmer, with nice graphical feedback in the text area. Plus you get deep control over the show metronome as well, as shown in the photo above. If you called
(use-sample-show)
as discussed above, as soon as you connect and power on your Push, Afterglow will activate its show control interface.
Moving on, though... we can change the global color to orange:
(show/add-effect! :color (global-color-effect :orange))
The color channel values change.
Let’s get a little fancy and ramp the dimmers up on a sawtooth curve each beat:
(show/add-effect! :dimmers
(global-dimmer-effect (oscillators/build-oscillated-param
(oscillators/sawtooth))))
Slow that down a little:
(afterglow.rhythm/metro-bpm (:metronome *show*) 70)
If you have a web browser open on your OLA daemon’s DMX monitor for Universe 1, you will see the values for channels changing, then ramping up quickly, then a little more slowly after you change the BPM. OLA 0.9.5 introduced a new, beta web UI based on AngularJS which you can access through a small New UI (Beta) link at the bottom of the page. In my experience, it has been completely stable, looks a lot better, and is far more dynamic and responsive at monitoring changing DMX values, and presenting them in an intuitive at-a-glance way.
If you can, alter the example to use a universe and channels that you will actually be able to see with a connected fixture, and watch Clojure seize control of your lights!
If you have DJ software or a mixer sending you MIDI clock data, you can sync the show’s BPM to it (see the Developer Guide for details, and for a Traktor controller mapping file that lets you sync to its beat phase information as well):
(show/sync-to-external-clock (afterglow.midi/sync-to-midi-clock "traktor"))
How about a nice cycling rainbow color fade?
(def hue-param (oscillators/build-oscillated-param (oscillators/sawtooth :interval :bar)
:max 360))
(show/add-effect! :color (global-color-effect
(params/build-color-param :s 100 :l 50 :h hue-param)))
Or, if you need to be woken up a bit,
(show/add-effect! :strobe (afterglow.effects.channel/function-cue
"Fast blast!" :strobe 100 (show/all-fixtures)))
The Developer Guide has more examples of building effects, and mapping parameters to MIDI controllers. There is also low-level API documentation, but the project documentation is the best starting point for a conceptual overview and introduction.
When you are all done, you can terminate the effect handler thread...
(show/stop!)
And darken the universe you were playing with.
(show/blackout-show)
An alternate way of accomplishing those last two steps would have been to call
(show/clear-effects!)
before(show/stop!)
because once there were were no active effects, all the DMX values would settle back at zero and stay there until you stopped the show.
When afterglow has important events to report, or encounters problems,
it writes log entries. In its default configuration, it tries to write
to a logs
directory located in the current working directory from
which it was run. If that directory does not exist, and you have not
explicitly configured a path to a log file, it assumes you are not
interested in the logs, and silently suppresses them. So if things are
not going right, the first step is to enable logging. You can either
do this by creating a logs
folder for Afterglow to use, or by
running it with the -l
command-line argument to set an explicit log
file path, as described in the Usage section above. If you
do that, afterglow will create any missing directories in the log file
path, and fail with a clear error message if it is unable to log to
the place you asked it to.
The Open Lighting Architecture’s web interface, which you can find on port 9090 of the machine running afterglow if you installed it in the normal way, can be useful in troubleshooting as well. You can see if the universes that afterglow is expecting to interact with actually exist, are configured to talk to the lighting interfaces you expect, and are sending DMX channel values that seem reasonable.
Although there are none known as of the time of this release, I am sure some will be found, especially if you are tracking the master branch to keep up with the current rapid pace of development. Please feel free to log issues as you encounter them!
Everything beyond this point in this document is written for people who are working on enhancing Afterglow itself.
If you are trying to learn how to use it, jump to the main Developer Guide page now!
Here is the set of tasks needed to cut a new release:
ffmpeg -i ~/Desktop/Cues.mov -pix_fmt rgb24 -r 10 -f gif - | gifsicle --optimize=3 --delay=10 > ~/Desktop/Cues.gif
CHANGELOG.md
to reflect the release:
make sure nothing is missing, and rename the sections to reflect the
fact that the unreleased code is now released, and there is nothing
unreleased.git commit -a
, git tag -a v0.2.0 -m "Release 0.2.0"
, git push --tags
.lein deploy clojars
.CHANGELOG.md
to include a new
unreleased section.To a large extent, this is now historical, and issue and enhancement tracking has moved to the issues system. There are still some interesting ideas here for longer-term consideration, though.
show/add-effect-from-cue-grid!
to launch all the nested
effects, recording their IDs. The effect will never return any
assigners, but will report that it has ended when all of the
nested effects have ended. Telling this effect to end will, in
turn, call show/end-effect!
on all nested cues (passing their
recorded id values as :when-id
, to avoid inadvertently killing
later effects run under the same key).:variable-overrides
parameter to
show/add-effect-from-cue-grid!
so compound cues can use it to
customize the values of parameters introduced by nested cues.CMAttitude
multiplyByInverseOfAttitude
to determine the
difference.mul
method).setEuler
to set a Transform3D
to a specific set of
rotation angles.(set! *warn-on-reflection* true)
at top of file.Copyright © 2015-2023 Deep Symmetry, LLC
Distributed under the Eclipse Public License 2.0. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software. A copy of the license can be found in LICENSE within this project.
Antora is used to build the Developer Guide, for embedding inside the application, and hosting on Netlify. Antora is licensed under the Mozilla Public License Version 2.0 (MPL-2.0).
Can you improve this documentation? These fine people already did:
James Elliott & The Gitter BadgerEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close