How does Liberation/Beyond compensate for galvo timing lag in safety masking?

I ran into an issue creating a beam mask in TouchDesigner to prevent the beam from going too low (yes, we are using black aluminum beam blockers and keeping them above 10 feet as well). We’re running a permanent installation (13 X-Laser projectors, EtherDream DACs, TouchDesigner for content and masking).

Our masking is a simple per-point threshold: blank RGB to zero once a point’s position crosses a boundary, position itself passes through untouched. On slow content it lines up cleanly. On fast content the effective masked edge shifts away from the boundary, and the shift scales with speed and flips with direction of travel.

Attached: a diamond at 60Hz with the mask through its center, right side masks correctly at the boundary, left side masks late, geometry visibly extends past where it should have blanked. Also attached: FM-modulated circle rings around 120Hz, same effect, much more pronounced at the higher frequency.

Our guess is a timing mismatch between the color/blanking decision and where the beam has actually physically gotten to, since the galvo and/or DAC can’t respond instantaneously to a fast-changing point stream. Derivative’s own EtherDream CHOP docs back this up in a related context, noting lasers are reflected by mechanical components with their own response characteristics, and scanning too quickly can visibly curve corners.

Since Liberation and Pangolin Beyond both handle masking natively rather than leaving it to the point stream, I’m curious if that’s specifically because of this class of problem. Does Pangolin Beyond/Liberation do any predictive or velocity-aware compensation on masking, biasing the blanking transition based on beam speed, or is the advantage more about calibrated hardware-specific timing data a generic point-stream approach in TD just doesn’t have access to?

Trying to figure out if this has to be solved close to the hardware, or if there’s a general technique we could apply upstream in TouchDesigner without Liberation/Beyond in the loop.

Our guess is a timing mismatch between the color/blanking decision and where the beam has actually physically gotten to, since the galvo and/or DAC can’t respond instantaneously to a fast-changing point stream.

Yeah, I imagine this is what is happening, especially if your masking is done on the generated signal points. When switching the laser signal on/off there needs to be some hold value to allow the laser beam position and the color to sync. This is why we add extra pre/post blank on/off points to the signal when the laser color switches on/off.

Effectively, when a shape goes out of bounds there is now a gap, so as far the laser signal is concerned these should be considered different shapes with all the necessary extra blanking points. This is actually what we do when calculating against the global laser bounds [-1, 1] and it’s done prior to generating the laser signal. However we don’t expose these bounds or allow for custom values, but I think this is something that would be useful, even if just allowing for custom rectangular masks. I will make an RFE for this.

In your case (without the aforementioned custom bounds feature) you would need to force your input to split the shape in two (or more, depending on the shape) when it goes out of bounds, then the Laser CHOP would handle the rest by performing blanking. You might be able to do this with the Delete POP, however you’d also need to create an extra point at the bounds intersection point so that the shape is still drawn completely to the bounding edge.

Update, in case it helps anyone hitting this.

Partial fix on the TouchDesigner side: split the stream, delay only the RGB channels by a fixed number of samples relative to X/Y, merge back before the Laser Device CHOP. Three native CHOPs, no scripting. Same idea as Pangolin’s Color/Blanking shift, built by hand. (Inserting a Laser CHOP to use its Color Delay parameter doesn’t work if your pipeline already produces a processed XYRGB stream — it wants to generate the point stream itself and re-scaled our XY by roughly 19x.)

At 48kHz, 6 samples of delay took the error from 4-6 inches down to about 1 inch at 14 feet. A diamond test now masks evenly on both sides at 60Hz, 120Hz and 200Hz, which is what you’d expect if the fixed component was a constant time offset.

The remaining inch is velocity-dependent — on FM/AM modulated content each pass crosses the boundary at a different speed, so one fixed delay is only exactly right at one velocity.

My guess as to why I can’t get closer: Beyond and Liberation work with points rather than a raw sample stream, so they can insert dwell points right at the boundary and let the galvo settle before blanking fires. Our content is pre-rendered to multichannel WAV, so we can gate samples but can’t insert them. If anyone has solved this at the raw point-stream level I’d like to hear how.

oops i added a new post to this thread without seeing your reply first. i wonder if forcing my image into two things would mess up how smooth the animation looks, since it was generated with continuous/raw signals from synths etc, suddenly making it into two shapes, i wonder how that would work.

are you saying that a custom “mask” feature thats very crude, just a box that you can use to cut off the bottom of the projection area or something like that might be possible by exposing these global laser bounds, or something like that? that sounds terrific if i’m interpreting you correctly.

Update, and a negative result worth recording.

I tried the obvious fix: split the stream, delay only the RGB channels by a fixed number of samples relative to X/Y, merge back before the Laser Device CHOP. Three native CHOPs, same principle as Pangolin’s Color/Blanking shift. (Inserting a Laser CHOP to use its Color Delay parameter doesn’t work on an already-processed XYRGB stream — it wants to generate the point stream itself and re-scaled my XY by roughly 19x.)

It worked on the mask. At 48kHz, 6 samples took the boundary error from 4-6 inches down to about 1 inch at 14 feet, holding at 60Hz, 120Hz and 200Hz.

But it’s unusable. Shifting RGB relative to XY applies to the entire frame, not just the boundary, so all my text and star/beam animations fell apart. That artwork is defined by precise blanking, and moving color relative to position smears it. Fine on continuously-lit shapes, fatal on everything else. Reverted.

Which sharpens the original question: Beyond and Liberation presumably insert dwell points at the boundary itself, keeping the fix local to the mask edge rather than shifting color globally. My content is pre-rendered to multichannel WAV, so I can gate samples but not insert them. Looks like an architectural limit rather than a tuning problem. Absorbing the residual with margin instead, but still interested if anyone has solved it locally at the stream level.

Update — this is now fully resolved. The masking edge was off because the color/mask decision was being made using the beam’s commanded position, not where the mirror had actually gotten to yet. The galvo has real mechanical lag, so at speed the mirror was still catching up when the mask decision fired, putting the visible edge in the wrong place. Fix: delay the color/mask decision by a small fixed number of samples (6 at 48kHz) relative to the XY position, right before the device CHOP. That lines the decision up with where the mirror physically is by the time it gets there, instead of where it was commanded to be. Once that was in place, the masked edge held a clean, consistent boundary on the actual projected beam.

2 Likes

are you saying that a custom “mask” feature thats very crude, just a box that you can use to cut off the bottom of the projection area or something like that might be possible by exposing these global laser bounds, or something like that? that sounds terrific if i’m interpreting you correctly.

Yes, precisely. A new set of parameters that would allow modification of the existing laser bounds [-1, 1] and all the masking and intersection logic that goes along with that. Anyway, I’m glad to hear to you’ve found a work-around in the meantime.

1 Like