Fixture Definitions

Fixture definitions tell Afterglow what capabilities a given piece of lighting hardware has, and how to control it to get the effects that are being requested.

Overview

There are a vast number of fixture types out there, and at this early stage almost none of them are built in to Afterglow, so you will probably need to create your own. You can study the existing fixture definitions for examples of how it is done. You can also ask for help on the wiki.

When you first embark on the project of creating a fixture definition, it might seem overwhelming. This page is long, and includes a great deal of complicated detail. But don’t despair! First, remember that most fixtures only do a subset of the kinds of things that the fixture definition mechanism needs to handle, so you do not need to learn and understand all of it at once. And even more helpful to beginners, realize that you do not need to finish your fixture definition before you can start using and testing it. Your fixture may be capable of many things, but start out by defining just a couple of channels that will make it at least light up, and see if you can get those working in a show. Once they are, you can already use the fixture at least at that level. And that success can be a foundation for digging deeper into other types of channels, which you can add to your definition one at a time, building on what you learn as you go along.

Also, as of version 0.1.3, Afterglow can read fixture definitions from the QLC+ lighting controller and use those to create a starting point for you. Since there are far more fixtures already defined for QLC+, that can be a huge help.

The namespace afterglow.fixtures has definitions for very basic one-channel generic dimmers and switches. Looking at the source for those can illustrate the most basic structure of a fixture definition. It also contains some helper functions Afterglow uses in processing fixture definitions.

The namespace afterglow.fixtures.american-dj contains definitions for fixtures made by American DJ. Currently, only the Hypnotic RGB laser is present.

The namespace afterglow.fixtures.blizzard is the most complete so far, mostly because the author lives close to Blizzard and mostly owns fixures made by them. It includes definitions for a full-featured moving head spot (the Torrent F3), a moving head color-mixing LED fixture (the Blade RGBW), an LED five-color Par (the Puck Fab5), an eight-head RGB system (the Weather System), and an RGBW moonflower effect (the Snowball). These might be useful examples for fixtures you want to create.

The namespace afterglow.fixtures.chauvet is for fixtures made by Chauvet.

Structure of a Fixture Definition

Once created, a fixture definition is simply a Clojure map with keys and values that tell Afterglow how to control it. The fixture definition functions linked to above simply create and return these maps.

Fixtures may have multiple "personalities" or modes which change the number and meaning of the control channels they use, and the features that are available. For such fixtures, the mode is generally given as an argument to the fixture definition function, and tells it what version of the map to return. For convenience, the function may let you call it with no argument, and assume a default mode. Its documentation should reflect this, see for example the blade-rgbw documentation.

When you are learning how to write fixture definitions, it can be helpful to compare the fixture definition functions, which call helper functions described on this page, with the actual maps that they return. This can be facilitated by using clojure.pprint/pprint to pretty-print the map that you get when calling the function. For example:

(clojure.pprint/pprint (afterglow.fixtures.blizzard/snowball))
 {:name "Blizzard Snowball",
  :channels
  [{:offset 1,
    :type :dimmer,
    :functions
    [{:start 0,
      :end 255,
      :range :variable,
      :type :dimmer,
      :label "Dimmer"}]}
   {:offset 2,
    :type :color,
    :functions
    [{:start 0, :end 255, :range :variable, :type :red, :label "Red"}],
    :color :red}
; [...many channels omitted for brevity...]
   {:offset 9,
    :type :control,
    :functions
    [{:start 0,
      :range :fixed,
      :type :no-function,
      :label "No function",
      :end 127}
     {:range :variable,
      :label "Sound-active",
      :type :sound-active,
      :var-label "Sensitivity",
      :start 128,
      :end 255}]}]}
; -> nil

Even though most of the channels were cut out to shorten that example, it is still pretty long. Still, it gives a sense of the shape of a fixture definition. You may have noticed that there were only two top-level keys in the returned map, :name and :channels. Those are the only two that need to be there for a simple fixture. And almost all of the information Afterglow needs is found in the :channels list, so that is where you will spend most of your work in creating the fixture definition.

If you had already looked at the source for snowball (which you can get to by clicking the view source button at the bottom of all the API documentation entries), you might have noticed that the source is a lot shorter than the resulting map. That’s because it can take advantage of a bunch of helper functions described on this page to help with the tedious and repetetive parts of constructing the map. And when you patch a fixture into a show using afterglow.show/patch-fixture! the map gets much bigger again, as Afterglow annotates it with information you provided during the patching process, assigning the actual universes and DMX channels for each logical channel in the fixture definition, and figuring out the location in space and aiming direction, potentially of several heads of a multi-head fixture.

So, let’s look at the fixture definition map contents in detail.

Key Purpose
:name

A string which identifies the manufacturer and model of fixture.

:channels

A list of channel specifications which tell Afterglow about the DMX channels that the fixture responds to, and how to make it do different things. This is the meat of the fixture definition, and is described in detail below.

:mode

If present, identifies the fixture mode in which this definition map was created. As desrcibed above, some fixtures can be configured to have different “personalities” which use a different number of DMX channels and provide a different set of features. Their fixture definition functions will use a mode argument to determine the mode in which the fixture is operating, and return an appropriate map. That map will include the chosen mode keyword as the value at this key.

:heads

If a fixture has multiple independent heads, which can be controlled individually, the channels which control the heads are grouped into a list under this key. Each entry in the list is a map which explains a single head. It will contain its own :channels key with the channel specifications controlling that specific head, and will also contain geometric information about the offset of that particular head from the geometric center of the fixture, so Afterglow can figure out where the head is in space when the fiture is patched into the show. This is described in more detail below.

:pan-center

If this fixture is a moving head capable of pan movements, this entry tells afterglow the DMX value to send the fixture to pan it directly at the audience when the fixture is hung at its standard orientation. (The documentation you create for your fixture definition needs to explain what this default orientation is, so that people patching your fixture can figure out the proper angle information to tell Afterglow if they hung it in a different orientation, as explained in Show Space.) The :pan-center value should pan the light so it is aimed exactly along the show Z axis when also tilted to :tilt-center.

Many fixtures can pan more than once around a full circle, so you may have a choice of values to supply here, all of which pan the fixture directly towards the audience in your default hanging orientation. If so, pick one towards the middle of the DMX range, giving Afterglow room to maneuver without having to flip to the opposite end of the pan range regardless of how the fixture has been hung.

If the fixture cannot pan far enough to aim directly at the audience when it is hung in its default orientation, you may be better off choosing a different default hanging orientation. But if you do not want to do that, you can set this to the closest value outside the legal DMX range which would cause the fixture to pan that far if it were legal and possible, and Afterglow will still be able to figure out and use the legal movements that the fixture is capable of.

:pan-half-circle

If this fixture is a moving head capable of pan movements, this entry tells Afterglow the amount it needs to add to the DMX value sent on the fixture’s Pan channel to pan it halfway around a circle in a counterclockwise direction. Afterglow uses this to figure out how to aim the head exactly where you want it. If your fixture is not capable of panning that far, this value may be larger than a legal DMX value. That is fine, Afterglow will figure that out. Simply always give it the value which, when added to some legal Pan channel value, would cause the fixture to rotate counterclockwise halfway around a circle if it could rotate that far. (This number could be negative if the fixture turns clockwise when the pan value is increased in its default hanging orientation.)

The Show Space page explains how to figure out which rotations are clockwise or counterclockwise with respect to different axes. Pan motions are rotations around the fixture Y axis.

:tilt-center

If this fixture is a moving head capable of tilt movements, this entry tells afterglow the DMX value to send the fixture to tilt it directly at the audience when the fixture is hung at its standard orientation. (The documentation you create for your fixture definition needs to explain what this default orientation is, so that people patching your fixture can figure out the proper angle information to tell Afterglow if they hung it in a different orientation, as explained in Show Space.) The :tilt-center value should tilt the light so it is aimed exactly along the show Z axis when also panned to :pan-center.

Some fixtures can tilt more than once around a full circle, so you may have a choice of values to supply here, all of which tilt the fixture directly towards the audience in your default hanging orientation. If so, pick one towards the middle of the DMX range, giving Afterglow room to maneuver without having to flip to the opposite end of the tilt range regardless of how the fixture has been hung.

If the fixture cannot tilt far enough to aim directly at the audience when it is hung in its default orientation, you may be better off choosing a different default hanging orientation. But if you do not want to do that, you can set this to the closest value outside the legal DMX range which would cause the fixture to tilt that far if it were legal and possible, and Afterglow will still be able to figure out and use the legal movements that the fixture is capable of.

:tilt-half-circle

If this fixture is a moving head capable of tilt movements, this entry tells Afterglow the amount it needs to add to the DMX value sent on the fixture’s Tilt channel to tilt it halfway around a circle in a counterclockwise direction. Afterglow uses this to figure out how to aim the head exactly where you want it. If your fixture is not capable of tilting that far, this value may be larger than a legal DMX value. That is fine, Afterglow will figure that out. Simply always give it the value which, when added to some legal Tilt channel value, would cause the fixture to rotate counterclockwise halfway around a circle if it could rotate that far. (This number could be negative if the fixture turns clockwise when the tilt value is increased in its default hanging orientation.)

The Show Space page explains how to figure out which rotations are clockwise or counterclockwise with respect to different axes. Tilt motions are rotations around the fixture X axis.

Channel Specifications

The :channels entry for a fixture or head definition map tells Afterglow the control channels that can be used to make that fixture or head do things. It is a list of maps, each of which describes the nature and capabilities of a single channel that the fixture or head responds to.

Although there is a lot of detail in this table, you don’t necessarily need to understand it all to create fixture definitions, because Afterglow provides channel creation functions to create these maps for you.

Each channel specification map has the following content:

Key Purpose
:offset

The number that identifies the channel. Each fixture listens to one or more channels, and is itself configured to a partcular DMX channel number (DMX channels range from 1 to 512). That configuration defines the first channel the fixture listens to. The :offset value tells Afterglow how the current channel specification relates to the fixture’s configured (starting) channel number. An offset of 1 corresponds to the first channel the fixture is listening to, which would be the channel number configured on the fixture’s front panel (or via its DIP switches or jumpers if it is really old-school). The second channel would have offset 2, and would correspond to the channel one greater than the fixture is configured to listen to.

Although it might seem more natural (at least to a programmer) to start the offset with 0, because then you could calculate the actual channel number by simply adding the offset to the address at which the fixture is configured to listen, most lighting manuals describe their fixture channels with numbers that start with 1, so Afterglow follows that convention.

The offsets for all the channel specifications in a fixture definition should form a continuous series of integers starting from 1 and going up to the number of channels the fixture supports. It is an error if more than one channel specification in the fixture definition uses the same offset value, and if there are any gaps it probably means that you have missed a channel specification (except for multi-byte channels, as described in the next row). You don’t need to define the channels in the same order as their offsets in your fixture definition, although that is a reasonable practice, making it easier to match them up with the manual.

:fine-offset

There is one circumstance in which there will be gaps in the :offset values for your channel definitions. Sometimes a pair of channels are used to express a single value, such as pan, tilt, or a dimmer level, because the normal DMX value range, from 0 to 255, does not give enough precision to allow smooth movements or fades. In those cases, you specify the channel number containing the most-significant byte (MSB) of the value as the :offset, and the channel containing the least-significant byte (LSB) is specified in the same channel specification using the key :fine-offset. The function afterglow.channels/fine-channel helps create such a channel specification map. (In fact, it has other handy features which make it useful even when you are creating a channel specification that does not need a :fine-offset value).

:type

Tells afterglow the kind of channel this is. Special values include :color for a channel that contains a color intensity, :dimmer for controlling brightness independent of color, and :pan and :tilt for controlling moving heads. Other channels may use keywords that Afterglow does not recognize. A common keyword used for a grab-bag channel which may do many things depending on the exact DMX value sent is :control.

:color

When the channel :type is :color, this key is also present to tell Afterglow what color the channel controls the intensity of. Afterglow uses this information to enable color mixing using multiple color channels. The value of this key will be a keyword. The values :red, :green, :blue, and :white are understood and supported for color mixing automatically. If your fixture has LEDs of other colors and you would like Afterglow to include them in its color mixing calculations, in addition to supplying a :color value for their channel, you will need to specify a :hue value (below), so Afterglow knows how to mix them in.

:hue

When the channel :type is :color, this key is optionally present to tell Afterglow the hue value of the LEDs controlled by the channel. This allows Afterglow to perform color mixing with non-standard LED colors. Its value is the numeric hue (expressed in terms of degrees around the color circle) of the LEDs. The best way to find that is with a colorimeter, but since most of us can’t afford them, you can approximate it by working with graphic design software, or even entering the color name on Wolfram Alpha.

If you don’t want Afterglow to mix colors using this channel, leave out the :hue entry. The fixture definition function for the Chauvet SlimPar Hex3 IRC uses optional keyword arguments to let the show creator decide whether or not to include them for its amber and ultraviolet channels.

:functions

A list of Function Specifications which identify ranges of DMX values that can be sent to the channel, and which perform particular functions. Fixture manufacturers often use a single DMX channel to achieve many different kinds of effects, in order to not use up the DMX address space, especially when it would not make sense to try to activate two or more of the functions at the same time. Afterglow effects and cues can work in terms of these function definitions, and it often makes sense to do so even for channels which implement only a single function, so you don’t need to worry about how a function is implemented when designing your effect or cue. Because of that, the channel creation functions add a function map even when you are creating a single-function channel.

:inverted-from

If this key is present, the value established by the channel’s assigners will be reversed when it is sent to the fixture. This is necessary to support fixtures which have inverted dimmer channels, and can be configured when creating the dimmer channel specification.

Head Specifications

As described above, the :heads entry in a fixture definition map is a list that describes each individually controllable head within that fixture. It may be a separate moving head, or it may just be an individually-addressable pixel. If a fixture has only one light-emitting head, it does not need a head specification list at all; everthing Afterglow needs to know about it will be contained in the main fixture definition. But if there is more than one place on the fixture that can be controlled independently, you will want to organize them into heads, and tell Afterglow their spatial relationships as well as which channels control which head, using a head specifications list. Each element of the list is a map with the following content:

Key Purpose
:channels

A list of channel specifications which tell Afterglow about the DMX channels that this individual head responds to. These have exactly the same structure as the channel specifications for the main fixture, as described above. A channel can only be listed in one place or the other. If it affects the entire fixture, it should be in the main list; if it affects only a single head, it should be in that head’s list.

:x

The offset along the fixture X axis, in meters, from the geometric center of the fixture (the point at which Afterglow is told the fixture is located when patching the fixture) and the geometric center of this head. If this head is centered along the fixture X axis, you can omit this value or you can supply it with a value of 0.0. The Show Space page illustrates the axes and links to a function you can use for converting inches to meters.

:y

The offset along the fixture Y axis, in meters, from the geometric center of the fixture (the point at which Afterglow is told the fixture is located when patching the fixture) and the geometric center of this head. If this head is centered along the fixture Y axis, you can omit this value or you can supply it with a value of 0.0. The Show Space page illustrates the axes and links to a function you can use for converting inches to meters.

:z

The offset along the fixture Z axis, in meters, from the geometric center of the fixture (the point at which Afterglow is told the fixture is located when patching the fixture) and the geometric center of this head. If this head is centered along the fixture X axis, you can omit this value or you can supply it with a value of 0.0. The Show Space page illustrates the axes and links to a function you can use for converting inches to meters.

:x-rotation

If this head aims in a different direction than the fixture as a whole, this value tells afterglow the angle in radians it is rotated around the X axis. The Show Space page illustrates the axes, explains how to calculate the sign of a rotation, and links to a function you can use for converting degrees to radians.

:y-rotation

If this head aims in a different direction than the fixture as a whole, this value tells afterglow the angle in radians it is rotated around the Y axis. The Show Space page illustrates the axes, explains how to calculate the sign of a rotation, and links to a function you can use for converting degrees to radians.

:z-rotation

If this head aims in a different direction than the fixture as a whole, this value tells afterglow the angle in radians it is rotated around the Z axis. The Show Space page illustrates the axes, explains how to calculate the sign of a rotation, and links to a function you can use for converting degrees to radians.

Function Specifications

Function specifications allow a single channel to be broken up into a series of value ranges which accomplish different purposes. As noted above, fixture manufacturers often do this so that they can provide a lot of functionality without taking up too much of the DMX address space. And since fixtures often have functions which cannot be activated at the same time, such as selecting a particular gobo on a gobo wheel, it makes great sense.

The :functions entry in a channel specification map lists all the functions that a given channel offers. In order to work well with Function Effects and Function Cues it is best to provide a function list even for channels which only perform a single function. A function list is a list of maps, each of which identifies a range of values that do something when the channel is set to a value within that range. Each map has the following content:

Key Purpose
:start

The beginning of the function range: the lowest DMX value which activates this function on the channel. Must be a legal DMX value, from 0 to 255, and less than or equal to :end. Ranges must not overlap, so this value must be greater than the :end value of any other function range defined for the channel.

:end

The end of the function range: the highest DMX value which activates this function on the channel. Must be a legal DMX value, from 0 to 255, and greater than or equal to :start. Ranges must not overlap, so this value must be less than the :start value of any other function range defined for the channel.

:type

A keyword which identifies the nature of the function. This is how Function Effects and Function Cues will find the effect, so it is important to be consistent when assigning function types. The list of standard function types is a good starting point. If you feel there is a common kind of function which should be added to that list, please open an issue requesting it.

:range

Tells Afterglow what kind of a function range this is. Some functions are simply either off or on, and even if multiple DMX values exist within the function range, the result of using any of them is no different from using another. Such functions are identified by a :range type of :fixed. Other functions, such as a rotation speed or focus, will have different effects for every value in the range, and are identified by a :range type of :variable. This helps Afterglow build an appropriate user interface for interacting with Function Effects in places like the Ableton Push Effect Control interface.

:label

Specifies a label that should be used when creating a user interface that refers to this function. Function Cues will use this as the label text in the grid cell they create in the web interface. If omitted, a capitalzed version of the value of the :type keyword (without its leading colon) is used as the label; this entry allows you to specify something more readable.

:var-label

Specifies a label that should be used when creating a user interface for adjusting the value associated with this function (so it makes sense to set this only when :range is :variable). Function Cues will use this as the label for the cue-local variable they create, and it will appear in places like the Ableton Push Effect Control interface. If omitted, the generic label “Level” will be displayed under the encoder knob.

:scale-fn

A function that will be called to scale the function value being requested by an effect. For functions whose :range is :variable, Afterglow function effects can vary the value being sent to activate the function. They normally do this as a percentage, where 0 maps to the :start of the range, and 100 maps to the :end, and values in between are scaled appropriately.

If there is a reason to tweak the values on the way in, you can store a function at this key in the function specification, and Afterglow will call the function with the percentage value the effect requested, and expect the function to return a modified percentage value to use to actually pick the DMX value to send. A good example of a reason to do this is with the strobe function, so that different fixtures can be coaxed into strobing at roughly the same rate. The fixture definitions that ship with Afterglow use afterglow.effects.channel/function-value-scaler to build :scale-fn functions for their :strobe functions so that, rather than a percentage, the strobe function value is interpreted as an approximate tenth-Hz rate (flashes per ten seconds), normalized for each fixture. The example in the Function Channels section below explains this further.

Channel Creation Functions

The afterglow.channels namespace provides a number of functions to help you create channel specifications in your fixture definitions. You will see these used all over the place in the fixture definitions which ship with Afterglow; here is an introduction to how they work.

Color Channels

afterglow.channels/color returns a channel specification for a channel that controls an individual color intensity (such as with an RGB LED fixture). Its two mandatory arguments are the channel offset (the channel number reported in the fixture manual, assuming they are numbered starting with 1 as described above), and the color, a keyword naming the color. The standard colors :red, :green, :blue, and :white will automatically participate in Afterglow’s color mixing for Color Effects. If your fixture has other color channels, and you would like them to participate in color mixing as well, pass the hue value of the color channel with the optional keyword argument :hue. (See the discussion above for ways to determine the hue value of your color channel.)

If your fixture supports two-byte color values for more precise color mixing, use the most-significant byte as the offset value, and pass the offset of least-significant byte using the optional keyword argument :fine-offset.

If you want to use a label which differs from the name of the color keyword in the user interface when adjusting Function Cues (for example, if the keyword is hyphenated, and you want the label to use a space), specify your desired label with the optional keyword argument :function-label.

Dimmer Channels

afterglow.channels/dimmer returns a specification for a channel that controls the dimmer of a fixture or head. It always takes at least one argument, the channel offset (as described above). If the fixture uses two-byte values for more precise dimmer control, use the most-significant byte as the offset value, and pass the offset of the least-significant byte using the optional keyword argument :fine-offset.

Normal dimmers are dark at zero, and get brighter as the channel value increases, to a maximum brightness at 255. However, some fixtures have inverted dimmers. If that is the case for the fixture you are defining, pass the DMX value at which the inversion takes place with :inverted-from. For example, fixtures which are brightest at zero and darken as the value approaches 255 would be specified as :inverted-from 0, while fixtures which are dark at zero, jump to maximum brightness at 1, then dim as the value grows towards 255 would be specified as :inverted-from 1.

Focus Channels

afterglow.channels/focus returns a specification for a channel that controls the focal plane of a fixture or head, usually a moving head spot which can project gobo (template) images. It always takes at least one argument, the channel offset (as described above). If the fixture uses two-byte values for more precise focus control, pass the offset of the channel that controls the most-significant byte as the offset argument, and pass the offset of the channel that controls the least-significant byte as the second argument, fine-offset.

Frost Channels

afterglow.channels/frost returns a specification for a channel that controls the frost effect of a fixture or head, softening the beam of light it emits. It always takes at least one argument, the channel offset (as described above). If the fixture uses two-byte values for more precise focus control, pass the offset of the channel that controls the most-significant byte as the offset argument, and pass the offset of the channel that controls the least-significant byte as the second argument, fine-offset.

Iris Channels

afterglow.channels/iris returns a specification for a channel that controls the iris (aperture) of a fixture or head, widening or narrowing the beam of light it emits. It always takes at least one argument, the channel offset (as described above). If the fixture uses two-byte values for more precise iris control, pass the offset of the channel that controls the most-significant byte as the offset argument, and pass the offset of the channel that controls the least-significant byte as the second argument, fine-offset.

Pan Channels

afterglow.channels/pan returns a specification for a channel that controls the pan (rotation around the Y axis) of a fixture or head. It always takes at least one argument, the channel offset (as described above). If the fixture uses two-byte values for more precise pan control, pass the offset of the channel that controls the most-significant byte as the offset argument, and pass the offset of the channel that controls the least-significant byte as the second argument, fine-offset.

Tilt Channels

afterglow.channels/tilt returns a specification for a channel that controls the tilt (rotation around the X axis) of a fixture or head. It always takes at least one argument, the channel offset (as described above). If the fixture uses two-byte values for more precise tilt control, pass the offset of the channel that controls the most-significant byte as the offset argument, and pass the offset of the channel that controls the least-significant byte as the second argument, fine-offset.

Zoom Channels

afterglow.channels/zoom returns a specification for a channel that controls the zoom of a fixture or head, changing how much the beam spreads as it travels from the fixture. It always takes at least one argument, the channel offset (as described above). If the fixture uses two-byte values for more precise zoom control, pass the offset of the channel that controls the most-significant byte as the offset argument, and pass the offset of the channel that controls the least-significant byte as the second argument, fine-offset.

Function Channels

afterglow.channels/functions returns a specification for a channel that implements a list of different functions for different ranges of DMX values. Its first two arguments are chan-type, the keyword which identifies the type of the channel (please see the list of standard function types below and try to reuse one if it is appropriate, or at least create your keyword in a way that follows their conventions), and the channel offset (as described above).

These are followed by a variable number of function range specifications, which take the form of a number (which identifies the starting DMX value for the function range) followed by the function specification itself. This can either be a function specification map as described above (without the :start and :end keys, which will be figured out from the starting ranges supplied to this function), or in many simple cases you can use the shorthand of passing a keyword, which will be expanded into a variable-range function with the a type of the keyword you supplied, or a string, which will be expanded into a fixed-range function with a type of a keyword made from the string you supplied. If you pass a nil after the number, it tells Afterglow to not create a function at all for that part of the range.

The range specifications need to be in order of increasing starting values, and the ending values for each will be figured out by context.

The best way to understand this is to look at an example, like the specification for channel 9 of the Torrent F3:

(chan/functions :shutter 9 0 "shutter-closed" 32 "shutter-open"
                           64 {:type :strobe
                               :scale-fn (partial function-value-scaler 14 100)
                               :label "Strobe (1.4Hz->10Hz)"
                               :range :variable}
                           96 "shutter-open-2" 128 :pulse-strobe 160 "shutter-open-3"
                           192 :random-strobe
                           224 "shutter-open-4")

This sets up a channel of type :shutter with offset 9. The remaining arguments are pairs which define function ranges.

The first two pairs use the String shortcut to set up a fixed-ranged function of type :shutter-closed from 0-31, and another fixed-range function of type :shutter-open from 32-63.

Then there is a more complex function specification, using the map approach to set up a variable-range function of type :strobe from 64-95, assign it a function label of Strobe (1.4Hz→10Hz), and assign it a scaling function, which maps the values from 14 to 100 onto tenth-Hertz frequency values, to try to normalize the strobe speed of the fixture, since :strobe is a very common function, and it is nice to try to get different models of fixtures to react similarly when a given value for that function is assigned to them.

The discussion of the :strobe standard function below provides another example of this approach, and explains it further.

This is followed by another fixed-range function of type :shutter-open-2 from 96-127 set up using the String shortcut, and a simpler variable-range function of type :pulse-strobe from 128-159 set up using the keyword shortcut rather than a map. That line finishes with a fixed-range function of type :shutter-open-3 from 160-191 created using the String shortcut. Since the Torrent’s pulse strobe mode is not something any of the other fixtures support, there was no need to try to use a scaling function to make it approximate another fixture’s speed.

The last two pairs should be easily understood by now, as we have seen their like before. The second-to-last line uses the keyword shortcut to create a variable-range function of type :random-strobe from 192-223, and the last line uses the String shortcut to create a fixed-range function of type :shutter-open-4 from 224 to the largest legal DMX value of 255. Again, random strobing is a function unique to the Torrent, so no effort was made to scale it.

The various shutter-open ranges all do the same thing, but need to be given different names, since function names must be unique; it is a quirk of this fixture that it has multiple ranges with the same function. Another valid approach for handling the redundant later ranges would have been to pass nil after the number to tell Afterglow to not create a function for them.

Generic Channels

If none of the above functions match the channel you are creating, you can use afterglow.channels/fine-channel to create the definition.

It always takes at least two arguments: chan-type, a keyword identfying the type of the channel (please see the list of standard function types below and try to reuse one if it is appropriate, or at least create your keyword in a way that follows their conventions), and the channel offset (as described above).

If the channel uses two-byte values for more precise control, use the most-significant byte as the offset value, and pass the offset of the least-significant byte using the optional keyword argument :fine-offset.

If for some reason the channel’s function type should differ from the value you gave for chan-type, you can pass a different keyword to use when creating the function range, using the optional keyword argument :function-type.

If you want to use a variable label which differs from the name of the channel’s function type keyword in the user interface when adjusting Function Cues (for example, if the keyword is hyphenated, and you want the label to use a space), specify your desired label with the optional keyword argument :var-label.

Function Creation Functions

There are also functions to help you create function specifications in your channel definitions.

Color Wheel Hue

afterglow.channels/color-wheel-hue returns a function specification which ties a color wheel position to a particular hue, so the color wheel can participate in Afterglow’s color effects. See the API documentation for more details, and the Torrent F3 fixture definition source for an example of its use.

Standard Function Types

Function Effects and Function Cues trigger and control specific functions, potentially across a range of different fixture types from different manufacturers. In order for that to work, the Function Specifications must be created with consistent :type keywords. When you are creating a new fixture definition, check to see if any of the functions that it provides are covered by this table, and if so, use the same keywords to identify them, so your fixture can participate with other fixtures in effects using that function.

If your function does not fit into this list, make up a keyword that makes sense for it, following the style shown here. And also please consider (if the function type is likely to be present on other fixtures and useful to other people) opening an issue requesting that your new function type be added to this list so that when other people create definitions for similar fixtures, they can interoperate with yours.

Function Key Description
:dimmer

Controls the overall brightness of the fixture or head, independent of any color intensity channels which might also affect it. This is also a fundamental channel type in Afterglow, and has a category of Dimmer Effects to work with it. Dimmer effects can work with either fully dedicated dimmer channels (in which case the channel itself has a :type of :dimmer, and the entire DMX range is used for dimming), or multipurpose channels in which a subset of the DMX range is assigned to a function of type :dimmer, and the channel :type is something else (like :control, as suggested below).

:red
:green
:blue
:white
:amber
:uv

These identify functions (usually entire channels) which control the intensity of a particular color, usually on LED fixtures. When you create a channel of type :color, it will have a :color key with this value, and a corresponding function range. If your fixture has LEDs of colors other than these, use the color name to identify the function. (This will happen automatically when you use the color channel creation function to create the channel.) Color channels are fundamental channel types in Afterglow, and the colors :red, :green, :blue, and :white will automatically participate in the color mixing Afterglow performs with Color Effects. The others can too if, as described above, the :color channel has a :hue entry.

:pan
:tilt

Rotates the fixture about its Y (in the case of :pan) or X (in the case of :tilt) axis. These are also fundamental channel types in Afterglow, and have categories of Direction Effects and Aim Effects to work with them.

:strobe

Causes the fixture to flash on and off abruptly (and usually rapidly). This is typically a variable-range function, so different values within the function range cause the fixture to strobe at different speeds. If possible, use a :scale-fn function (with the help of afterglow.effects.channel/function-value-scaler) when creating a strobe function so that the function level is interpreted as an approximate Hz rate for the strobe, and your new fixture will strobe in rough tandem with other fixtures being strobed.

Take a look at the strobe function definitions for the existing fixtures for examples how to do this. All you need to do is measure the slowest and fastest rates at which your fixture actually strobes, as best you can, and use them like this:

(chan/functions :strobe 7
                0 nil
                11 {:type :strobe
                    :scale-fn (partial function-value-scaler 6.6 100)
                    :label "Strobe (0.66Hz->10Hz)"
                    :range :variable})

In this example, the fixture’s strobe channel is at offset 7, and the range from 0-10 does not strobe (the nil function specification tells Afterglow to skip creating a function for that range), while at 11 it begins to strobe approximately 0.66 times per second (or 6.6 times every ten seconds, which gives a more useful spread of strobe values across the normal function value assignment range of 1-100), and strobes faster for higher values, finally reaching around ten times per second at the maximum function value of 100.

Measuring the actual strobing rate of arbitrary fixtures is difficult to get right, I am not yet quite satisfied with the scaling function values for my lighting rig, but for all practical purposes, the audience does not notice the difference when being dazzled by strobes.

:focus

Adjusts the focal plane of the fixture, usually a moving-head spot with the ability to project gobos (templates).

:frost

Controls a frost effect, softening the beam of light.

:iris

Controls the iris size, widening or narrowing the beam of light.

:zoom

Adjusts the rate at which the beam spreads as it travels further from the fixture.

:sound-active

Puts the fixture in a mode where it decides what to do by listening to music in the environment, rather than being directly controlled by its DMX channels.

Translating QLC+ Fixture Definitions

QLC+ is an established and powerful free and open-source lighting control system aimed at more traditional workflows than Afterglow. If you were not already aware of it, you should definitely take a look. And since it has been around a while, used by an increasing variety of people, it has had time to accumulate a bunch of fixture definitions for lights that you are likely to encounter or own.

Even though QLC+ does not model fixtures in as much detail as Afterglow, so their definitions are incomplete from our perspective (lacking geometry information for aim and direction cues, and explicit links between channels that pair up to control a single fixture function, among other things), Afterglow can still use them as a starting point to help you creating a fixture definition, and save a whole lot of time reading fixture manuals, and trial and error…​ especially when it comes to channels with a lot of functions, like gobo wheels. So when you decide to create an Afterglow fixture definition, start by looking to see if QLC+ already has one for that fixture.

You can find its current set of fixture definitions on GitHub. If you see one for the fixture you want, you can either click on it and download it individually (after choosing the Raw view for the file in its header bar), or, if you are already using git, you can clone the entire project to get local copies of all the fixture definitions.

Once you have downloaded the QLC+ fixture definition file, you can invoke Afterglow from the command line, as described in the Usage section on the project page, to translate it into an Afterglow fixture definition. For example, translating the definition for the American DJ Eco UV Bar, like so:

% java -jar afterglow.jar -q American-DJ-ECO-UV-BAR-DMX.qxf
Translated fixture definition written to eco-uv-bar-dmx.clj

would result in the following Afterglow fixture definition file:

(ns afterglow.fixtures.american-dj
  "Translated definition for the fixture ECO UV BAR DMX
  from American DJ.

  This was created by Afterglow from the QLC+ Fixture Definintion
  (.qxf) file, and will almost certainly need some manual adjustment
  in order to enable full Afterglow capabilities.

  If you have more than one fixture definition for this manufacturer,
  you can consolidate them into a single file if you like, with a
  single copy of this namespace definition, since it is the same for
  all fixture definitions translated by Afterglow.

  Once you have completed the fixture definition, and are happy with
  the way everything is being controlled by Afterglow, please consider
  submitting it for inclusion with Afterglow, either as a Pull Request
  at https://github.com/Deep-Symmetry/afterglow/pulls if you are
  comfortable putting that together, or just on the Wiki if that's
  easier for you:
  https://github.com/Deep-Symmetry/afterglow/wiki/Questions#defining-fixtures

  The original fixture defintition was created by Rob G.
  using Q Light Controller Plus version 5.0.0 GIT.
  QLC+ Fixture Type: Other"
  (:require [afterglow.channels :as chan]
            [afterglow.effects.channel :as chan-fx]))

(defn eco-uv-bar-dmx
  "ECO UV BAR DMX.

  Please flesh out this documentation if you are submitting this for
  inclusion into Afterglow. See, for example, the Blizzard fixture
  definitions:
  http://deepsymmetry.org/afterglow/api-doc/afterglow.fixtures.blizzard.html"
  []
  {:channels [(chan/color 1 :uv)  ; TODO: add :hue key if you want to color mix this
              (chan/fine-channel :strobing 2
                                 :function-name "Strobing"
                                 :var-label "Strobing (slow -> fast)")
              (chan/functions :dimmer-curve 3
                              0 {:type :dimmer-curve-no-dimmer-curve
                                 :label "No dimmer curve"
                                 :range :variable}
                              21 {:type :dimmer-curve-dimmer-curve-1
                                  :label "Dimmer curve 1"
                                  :range :variable}
                              41 {:type :dimmer-curve-dimmer-curve-2
                                  :label "Dimmer curve 2"
                                  :range :variable}
                              61 {:type :dimmer-curve-dimmer-curve-3
                                  :label "Dimmer curve 3"
                                  :range :variable}
                              81 {:type :dimmer-curve-dimmer-curve-4
                                  :label "Dimmer curve 4"
                                  :range :variable}
                              101 {:type :dimmer-curve-delay-mode-control
                                   :label "Delay mode control"
                                   :range :variable})]
   :name "ECO UV BAR DMX"})

Of course this is a very simple fixture, but I didn’t want to waste a ton of space on the example, and it shows the basic idea.

The new definition file will be written to the same directory as the .qxf file it was based on. It is not named in a way (nor placed in the necessary directory hierarchy) that would enable it to be loaded using a normal Clojure require form, because it is intended to be loaded individualy using Afterglow’s init-file mechanism, also described in Usage, and within afterglow-max, by the load-init-file function. If you are creating definitions for several fixtures from the same manufacturer, you are encouraged to combine them into a single file, as described in the API documentation at the top of the example above, using your favorite text editor. The ns form places the fixture definition functions in a package named after the manufacturer, and so needs to appear only once at the top of the file, and all the fixture definition functions themselves can be listed after it.

Using the fixture definition from this example, once the file is loaded, is as simple as calling (afterglow.fixtures.american-dj/eco-uv-bar-dmx) within show/patch-fixture!.

What’s Missing from Translated Fixture Definitions

As mentioned in the introduction, there are some things that Afterglow simply cannot guess from translated fixture definitions. Even in simple cases like this example, you will find things that you can make better by hand-editing the results based on your understanding of the fixture, after reading its manual or working with it for a bit.

Function Specifications

First off, all fixture function ranges are created as :variable, meaning that they do slightly different things along the range of values that activate that function, because QLC+ does not distinguish between fixed and variable functions. In the event that the function actually has no adjustable behavior, you will want to change :range :variable in the corresponding function specification entry to :range :fixed, so that the user interface of a function cue created for this fixture properly reflects the fixture’s behavior. I am pretty sure that is something that should be done for all the ranges in this example, but I don’t have the actual fixture to test it and see. Function specifications are explained in more depth above.

The types and labels assigned to the function ranges are derived from the labels in the .qxf file, and uniqueness is enforced, but they are probably too long in many cases (especially if you want them to be readable in the Web or Ableton Push interfaces), and in some cases should be adjusted to match up with the standard types so that they can automatically work with cues. (This is especially likely to be the case with strobe cues, for example. Compare the translated definitions with some that ship with Afterglow as you are starting to get a feel for these issues.)

Channel Types

Afterglow tries to guess what kinds of channels it finds, based on their name, and aspects of their structure. For simple cases it will get it right, and save you time, but it might be wrong. This is especially important to double-check for dimmer channels, to make sure they are properly detected, since only then will they participate in Dimmer Effects and the Master chain. (And you don’t want inappropriate channels to be mapped as dimmer channels for the opposite reason.)

Dimmer Channels

In addition to the possibility that a dimmer channel might be misidentified, as described above, some fixtures have inverted dimmer channels, which do not get brighter as the DMX value increases. The .qxf file does not record this information, so you will need to manually add it to the specification.

Color Channels

Colors are fairly well represented and identified in the QLC+ format, and if you have a channel controlling a red, green, blue, or white channel, chances are good that it will be properly translated. If you have an LED fixture with other colors, like amber, UV, or beyond, and want these other channels to participate in Afterglow’s automatic color mixing capabilities, as noted by the TODO: comment in the translation example above, you will need to add a :hue key to the color channel definition, containing the actual hue value of the LEDs controlled by that channel. The Chauvet SlimPar Hex IRC definition that ships with Afterglow contains a nice example of doing this for its amber and UV channels, and shows how to make this extended color mixing an optional feature when using the fixture definition.

Two-Byte Channels

DMX parameter values are integers which only range from 0-255. That is not enough to achieve precise pan and tilt movements, and some fixtures even want to allow more precise dimming values and color intensities. In order to achieve that, they use more than one channel to communicate a single parameter value. QLC+ fixture definition files reflect this to an extent, using their Group tags which have a Byte value of 0 or 1. But there is no explicit link in the .qxf file between the channels that are controlling the same value. Afterglow is able to figure it out in simple cases, such as where there are two channels controlling the intensity of the color red, using bytes 0 and 1. But if there are more than two channels serving the same purpose, it cannot figure out the relationships, and you will have to sort that out using the fixture’s manual. Once you do, get rid of the channel specification for the least-significant byte in the fixture definition, and specify that channel as the fine-channel value for the channel specification of the most-significant byte, as documented in the Channel Creation Functions section.

Geometric Information

If the fixture includes a Pan or Tilt channel, you will see additional TODO: comments telling you that you need to add information about how the channel actually physically rotates the fixture, in order for Afterglow to be able to accurately calculate Direction and Aim effects with it. The Structure section above describes the :pan-center, :pan-half-circle, :tilt-center, and :tilt-half-circle values that you will need to figure out experimentally.

We hope to someday help automate part of this process, which will make it easier for all of us!

Similarly, if the fixture has multiple heads, you will see TODO: entries where you need to fill in their locations relative to the origin of the fixture itself, so that Spatial Parameters can work properly with them.

Don’t Panic!

Even though this sounds like a lot of things that can go wrong trying to build an Afterglow fixture definition, most of the problem areas are subtle, or relate to the more advanced capabilities of Afterglow. Chances are very good that the automatically generated fixture definition will at least enable basic control of the fixture right away. So try that, and gain some confidence, as you gradually explore the areas where it isn’t quite doing what you want, and tackle those one at a time. And, as you do, remember that you can get help:

When Things Go Awry

Not all definitions have been tested with the translator, and there may be scenarios that it gets fundamentally wrong. If so, please raise an Issue, so we can see if it is something that can be fixed, or a fundamental limitation of the translation approach that should be documented here.

If you are having trouble figuring out the details of how to finish or use your fixture definition, please ask for help on the Zulip chat or Wiki. Not only will that hopefully get you going faster, but it might help others in the future, especially if it leads to improvements in the documentation or Afterglow itself.