[Flamecaster - Fixture Mode] WIP Framework to control Pixelblaze with DMX

I have been playing for a while with Flamecaster’s fixture mode that allows to control a Pixelblaze with DMX.
It works quite well, and the possibilities are massive. I’m just scratching the surface but I have a LOT of ideas.

You can see below a DMX spot (connectedby an Artnet to DMX box) on the mountain synced in color with the ring driven by a Pixelblaze. Nothing fancy so far.

To make it easier to work with, I’m trying to create a framework I could copy in any pattern I have (or will) develop-ed that would allow control of a few variables directly with the DMX channels, instead of the usual UI sliders. The goal is to fully control a Pixelblaze from an Artnet output without having to connect to the UI.

I’m also trying to standardize as much as possible this framework to make it easy to work with, and as light / customizable as possible to minimize impact on actual pattern calculations.

So far I’m grabbing RGB channels as well as a dimmer successfully (with conversion of the RGB to HSV to make it easier to use). I think I won’t have much trouble to add more channels as “variables”.

My main issues are following:

  1. How can I detect I’m actually in DMX mode or not ? I was thinking if all channels are at zero, all DMX calculations and calls are “not done” to save CPU and the pattern eventually runs in “standalone” like a normal pattern, for example with rotating colors, or with classic UI control. At the moment I just check if all my channels are at zero, but there is a delay at pattern start to get the channels (avg. 1 sec) which means it always starts “without DMX”… Not ideal if I want to change patterns like below.

  2. How to change properly a pattern through DMX input ? I had the idea to select any pattern based on the channel[4] value (for example 0 = no change, 1 = pattern 1, 2 = pattern 2,…, 254 = previous pattern, 255 = next pattern), then only confirm the change if channel[5] is put to 255. The issue is that if it stays at 255, it’s constantly changing pattern, so I tried to create a code that requires it to be reset to 0 before registering a new “selection”, but it does not work (probably because as in 1. it seems the DMX channels are seen as zero when the pattern starts, even if Flamecaster is sending something). Maybe there’s a simpler way ?

  3. Is it useful to detect if a DMX channel has been changed ? Like this we could avoid calculate HSV values before each frame for example. I’m not sure how it conflicts if DMX frames are dropped due to unstable connection ?

Once this framework is working fine, I plan to have channels sending:

  • Pattern selection
  • Master Dimmer
  • RGB + Dedicated dimmer + Speed (BPM) in a way we can send several sets for each “segment” so the pattern can interpret it as “layers” allowing to blend effects in one pattern

As well as a sound/movement framework on top to enable sound and movement processing in-house as well: Flamecaster has too much latency to send sound data trough channels from the console, except BPM of course.

Thank you !

THE CODE:

// Framework by HOLOLIT
export var channels = array(12) //Number of channels
export var DMXActivated = 0

//convert RGB to HSV: output sets hDMX,sDMX,vDMX globals
function rgb2hsv(r, g, b) {
  var rr, gg, bb, diff

  r = clamp(r, 0, 1)
  g = clamp(g, 0, 1)
  b = clamp(b, 0, 1)

  vDMX = max(r, max(g, b))
  diff = vDMX - min(r, min(g, b))
  if (diff == 0) {
    hDMX = sDMX = 0
  } else {
    sDMX = diff / vDMX
    rr = (vDMX - r) / 6 / diff
    gg = (vDMX - g) / 6 / diff
    bb = (vDMX - b) / 6 / diff

    if (r == vDMX) {
      hDMX = bb - gg
    } else if (g == vDMX) {
      hDMX = (1 / 3) + rr - bb
    } else if (b == vDMX) {
      hDMX = (2 / 3) + gg - rr
    }
    if (hDMX < 0) {
      hDMX += 1
    } else if (hDMX > 1) {
      hDMX -= 1
    }
  }
}

export var DMXNext = -1  //Initializes at -1 means if DMX06 (next program if = 255) needs to be reset at 0 before being taken into account

export function beforeRender(delta) {
  
  
  /* Gettng DMX Channels */ {
  if (arraySum(channels)==0){
    // Execute the pattern without DMX (standalone mode)
    DMXActivated = 0
    
  } else {
  
    DMXActivated = 1
    DMX01 = channels[0] / 255   // Dimmer             (Segment 1)
    DMX02 = channels[1] / 255   // Red                (Segment 1)
    DMX03 = channels[2] / 255   // Green              (Segment 1)
    DMX04 = channels[3] / 255   // Blue               (Segment 1)
    DMX05 = channels[4]         // Pattern pre-selection (255 = next pattern)
    DMX06 = channels[5]         // Pattern confirm (selects if reset then put to 255)
    DMX07 = channels[6] / 255   // Timer 3
    DMX08 = channels[7] / 255
    DMX09 = channels[8] / 255
    DMX10 = channels[9] / 255
    DMX11 = channels[10] / 255
    DMX12 = channels[11] / 255 }
    
    rgb2hsv(DMX02, DMX03, DMX04)  //Calculates HSV based on RGB DMX channels
    vDMX = vDMX * DMX01           //Adds dimmer on top
  
    //Detects if DMX6 has been reset before activating it
    if (DMX06 == 0){
      DMXNext = 1
    }
    
    if (DMX06 == 255 && DMXNext == 1){
      //New input to change program
      if (DMX05 == 255) {    sequencerNext() }
      //We can imagine other values of DMX05 could select a specific pattern ?
    }  
  
  }
}

export function render(index) {
  
  if (DMXActivated == 0){
    hsv(0,0,.2)
  } else {
    hsv(hDMX,sDMX,vDMX)
  }
}

A couple of ideas:

  • To detect if DMX is active, initialize your channels to values that DMX can’t send, like -1; Then, if sum of the channels is negative, you know that no DMX data has been received yet.

  • The simple way to tell if variables have been changed between frames is just to keep a duplicate copy:

var lastv1 = 0;
export var v1 = 0
.
.
.
if (v1 != lastv1) {
  // do stuff with new value
  lastv1 = v1
}
else {
  // value didn't change
}

You could do this only for important channels, or since there’s not a huge amount of data, for everything. It’s not the most elegant looking approach, but it is very reliable.

I may still add an “only send data when it changes” option to Flamecaster. It’ll reduce network bandwidth when used with DMX sources that send constantly. But it will only partially solve your problem, because if any channel in a universe changes, that whole universe will be sent because of the way Art-Net works.

Hello !

I’m back with extensive testing done on the Fixture Mode of Flamecaster.
I have 2 use cases I would like to describe to try to find a solution to my issue.

Configuration:

  • 1 laptop running QLC+ with a MIDI controller and Flamecaster sending to the Pixelblaze, connected in ethernet to my router
  • QLC sends Universe 1 to an Artnet to DMX driver to control LED washers (this controller is plugged in ethernet to the router), and Universe 2 to Flamecaster on the same computer
  • The pixelblaze is exposed 4m from the router (between -53 and -60 dB) is driving a 815 data line at about 35-40 FPS. It’s the only wifi device connected to this router, at my home, though I have another Wifi network active nearby
  • The fixture mode in Flamecaster is configured for 12 channels on the Pixelblaze at 30 FPS

Case 1 (works):

  • QLC seems to send channels only when I move a MIDI fader. Flamecaster displays 0.3-0.7 FPS IN/OUT when I don’t touch any fader.
  • If I wiggle the fader, FPS reaches about 20-21 FPS in Flamecaster
  • Colors/channel effects changes are instantaneous. No visible delay

Case 2 (problem):

  • I’m trying to send QLC’s BPM clock, basically a channel going progressively from 255 to 0 with every beat (so about 480ms), or even half beat if possible, constantly changing value to be used in the pattern for example with “wave”. The idea is to be able to exploit the “BPM Tap” + mutliplier directly in the pattern without additional computing
  • QLC’s DMX monitor shows the channel values going from 255 to 0 pretty smoothly, all values of the channel seem to be hit through time
  • Flamecaster shows only 21 FPS OUT, but the PPS is 50 (probably the framerate output of Pixelblaze) !
  • I lose many packets and not only I miss many values of the BPM clock on the Pixelblaze, but now moving any color slider gives me a several (3-5 sec) second delay, changing after like the instruction was in a queue. Seems either Flamecaster or the Pixelblaze can’t keep up with the data framerate. Though 1 universe is like 200 kb/s, so I don’t get why wifi can’t keep up, that’s pretty much the only data I send.

So I think the issue is not really the volume of data (I sent only 12 channels but I guess it doesn’t matter as a whole universe is used), but more the framerate. It would be great if it was possible to have a rather stable 30 fps, else it’s really hard to send live data to Pixelblaze: looks laggy.

I checked around and it seems the only way is to modify the framerate sent by QLC+ is to change the master clock of QLC+, but this is done through registry modifications and possibly messing the whole software. Q Light Controller Plus - ArtNet Plugin

I will continue to investigate. Another solution would be to send the actual BPM as a channel value (but I can’t manage to find how to link this to the Tap clock in QLC), then calculate a clock on the Pixelblaze, but this does not sync me with a beat, only giving a tempo that I have to process with a sensor board to try to find the beat (requires the sensor board): complicated…

I’m open to suggestions.

Update: instead of running my BPM Clock DMX channel from 1 (0 means we don’t use it so the pattern reverts to the sensor board tempo detection) to 255 by sending all the values in between, I simply send 255 for a full beat duration, and 1 for the next beat.
By comparing the value change on this channel (channels[5]) vs last frame (chDMX[5]), I detect the beat, and I send much less DMX data to Pixelblaze (only 0 and 255 per clock). Works much better and it does not miss one beat.

Then I smooth the values in between in the Pixelblaze code. The issue with this method is that I can’t smooth a raising edge, only the decay (thank you @jeff for the code) once the beat change is registered by Pixelblaze. Also, my “beat” decay is fixed, even if I send only x1/2 or x2 clock… Open to suggestions on this.

if(chDMX[5]==0){
       //No BPM clock is sent through DMX
       BPMClockSmooth=1//If no clock is detected I keep this variable at 1
     }
     else{
       //BPM clock is sent at 1 or 255
        if (chDMX[5]!=channels[5]/255){   //If BPM clock square signal change of status 1 or 255)
          BPMClockDMX=1                   //BPM kick activated for 1 frame only
          BPMClockSmooth=1
        }
        else{         //Disables BPM kick frame only if it has been activated once, else it stays disabled at -1
          BPMClockDMX=0
          
          delta = delta || 3 // Test result: delta is 3 ms for 320FPS on v3 with 128 LEDs
          seconds = 1.5
          var decayCoeff = pow(2, log2(.99) * delta / seconds / 2)
          BPMClockSmooth = clamp(BPMClockSmooth*decayCoeff,0.001,1)
        }
      }

      // Transfers & Normalize DMX channels to chDMX
      for (var i = 0; i < nbDMXChannels-1; i++) {
        chDMX[i] = channels[i] / 255;
      }

Would still love be able to send at least a dozen channels at 30-40 fps for advanced purposes (and it would simplify the code in Pixelblaze).