Changing Range Sliders to AnalogInputs

There are a lot of range sliders in patterns that I’d like to control using analogInputs on the sensor board instead of the UI sliders. However, the sensor expansion board variables don’t seem to work unless they are called within the beforeRender or Render functions and many of the sliders are defined in the first part of the script. Is there a simple tutorial for converting a slider to an analogInput variable? Any help is appreciated.

Your slider moves a variable. You can move this same variable using a call to a GPIO.
The only drawback I’ve observed is that if you change the status of a variable in the code that can be modified using a slider, there is no way to update the slider’s position accordingly.

Let’s say you have a simple slider like this:

var myValue
export function sliderMyValue(v) {
  myValue = v * 2
}

First, you’d probably want to disable it as a UI control so that you aren’t given a slider anymore, that would be overridden by the analog input anyway. The easy way to do that is to remove the export keyword.

Next, you can call the function with an analogInput[] value (or an analogRead() call). Most slider implementations are straightforward and update a variable without too much code. Rather than duplicating the expression, we can call the old UI control handler function. This can be done in beforeRender().

export var analogInputs //need this in the code
var myValue
function sliderMyValue(v) {
  myValue = v * 2
}
//...
export function beforeRender(delta) {
  sliderMyValue(analogInputs[1])
  //...
}

Now some controls might not handle being updated constantly, or might cause problems if the value is constantly changing, even if its just by a tiny bit. For instance speed controls that impact values passed to time(). There’s a few that do some heavy recalculation when controls are updated and it’s expensive to call them every animation frame.

To solve for that, you can add a little hysteresis and only call the control handler if the value changes more than a certain amount. Here’s an example of a speed slider that is converted to reading from an analogInput:

export var analogInputs

//example slider control with export removed
var speed
function sliderSpeed(v) {
  speed = 0.1 + v * 2
}

var hysteresis = .05 //how much the value has to change before the control handler is called

function hysteresisControl(oldValue, currentValue, controlHandler) {
  if (abs(oldValue - currentValue) > hysteresis) {
    controlHandler(currentValue)
    return currentValue
  }
  return oldValue
}

//new variable for keeping track of the last reading that caused the control to update
var lastAnalogInput1

export function beforeRender(delta) {
  //NOTE we are passing in a function by reference (the slider control handler)
  lastAnalogInput1 = hysteresisControl(lastAnalogInput1, analogInputs[1], sliderSpeed)
  t1 = time(speed)
}

//example rainbow
export function render(index) {
  h = t1 + index/pixelCount
  s = 1
  v = 1
  hsv(h, s, v)
}

I’ve had plans to add some UI binding of sliders to things like that. Many of the controls were designed with this in mind. The idea is that you could hit a gear icon in the controls area and bind any slider to an analog value from a sensor board or GPIO, toggle controls could bind to digital inputs, and trigger controls could come from digital inputs with some debounce logic added.

Thank you for the detailed explanation. I will give it a shot today.

I’ve been able to get around it in most cases by multiplying the variable by the GPIO value, but it doesn’t work in all cases and this sounds like a much better solution. Thanks for the help.

That would be very very helpful.

Thanks again, wizard. That worked very well.

My workaround previously for the hystereses problem has been:

speed = round(20 * analogInputs[1]) / 20

But, I’m not sure that will work in every situation.