XOD has built-in nodes to help put this together. There is an “led” node to control the LEDs. There is “fade” node that basically implements the morph function. Performing the steps in sequence gets a little tricky in this case.
The easiest way to sequence actions in XOD is to use pulses to control program flow. A direct implementation to just turn the LEDs on & off would look like this:
Which is basically a variation of the basic traffic light example in XOD documentation
https://xod.io/docs/guide/simple-traffic-light/ The problem with this is it is difficult to insert the fade node into this code and have it work correctly.
The simplest brute-force option is probably to note that this code has 9 states:
1 everything off
2 Only red on
3 Red & green on
…
8 Red and Blue on
9 Only Blue on
Loop back to the 1st step.
Knowing this, we can setup a clock & counter to loop through the 9 steps, then use nth-input nodes to select the value for each LED. Note that XOD starts counting at 0, so XOD will be using steps 0-8 rather than 1-9. Adding the fade node before the led node causes the LED to slowly come on & go off.
Note that we do not actually wait for each LED to reach its final value before continuing. Instead, we set the fade function to take 5 seconds to go from 0 to 1 (or 1 to 0), then set the counter to increment every 5 seconds. We could change this so count increments every time all the fade values reach their target value. This replaces the clock node with “equals”, “and”, and a “defer” node to loop back to the count node.
Another alternative is to replace the nth-input nodes with “select” nodes. These require a pulse to determine which value to pass out. This gets us a little closer to the traffic light example, except that we only have one “led” node for each LED that is always active instead of activating the led node with the value we want.
Rather than crossing wires all over the place, I’ve used bus nodes in this example (from-bus & to-bus). Buses with the same label are basically linked by invisible wires. On the right side is a stack of delay nodes that control when each change happens by sending a pulse to select what value we want for each LED. Note that you could link any pulse to multiple LED values instead of just one if you wanted to change more than one at the same time. Green & Blue LEDs only turn on/off once each. Since the Red LED turns on/off multiple times in the cycle, we need “any” nodes to join the multiple inputs.
Like the earlier example, this one has set delays between each step. If you want to trigger each step when LEDs reach their target value, You’ll need the same equal/and logic to determine when we are ready to step. The delay nodes also need replaced. In this case, we need a way to remember which step we are on. One way to do that is to use a flip-flop for each step. The pulse to continue gets sent to all the steps, but only the step that has the previous step active receives the pulse because of the “and” nodes. We also have a “defer” node to reset the previous step.
Here is just the 1st couple steps implemented:
As you can see, there are usually multiple ways to program a single action. Which one is “correct” is not always obvious. It may not matter at all. Inputs needed for other parts of the program might make one an obvious choice. In this example, you might have other actions going on that need to know which step you are in. Having a counter might make that easier…or perhaps pulses will make it easier depending on what you are doing.
In this case, if you didn’t need the fade function, the 1st example would probably be the easiest solution. The way the fade node works, it is not easy to insert it into that code. Since you are always fading from 0-1 or 1-0, you could write your own specialized delay/fade node (morph) that would initialize to the opposite number from your target on SET pulse, then provide outputs for current value, ACT (active) boolean, and DONE pulse so that it could replace the delay nodes.
Morph node (copy of fade node with extra pins & code):
#pragma XOD evaluate_on_pin disable
#pragma XOD evaluate_on_pin enable input_UPD
#pragma XOD evaluate_on_pin enable input_SET
struct State {
TimeMs lastUpdateTime;
};
{{ GENERATED_CODE }}
void evaluate(Context ctx) {
State* state = getState(ctx);
if (isInputDirty<input_SET>(ctx)) {
Number start = !getValue<input_TARG>(ctx);
emitValue<output_OUT>(ctx, start);
emitValue<output_ACT>(ctx, true);
}
if (!isInputDirty<input_UPD>(ctx)) {
return;
}
TimeMs now = transactionTime();
Number target = getValue<input_TARG>(ctx);
Number position = getValue<output_OUT>(ctx);
if (target == position) {
// Already done. Store timestamp anyway so that an animation to a new
// value would not jump at the first update
state->lastUpdateTime = now;
if (getValue<output_ACT>(ctx)) {
emitValue<output_DONE>(ctx, true);
emitValue<output_ACT>(ctx, false);
}
return;
}
Number rate = getValue<input_RATE>(ctx);
TimeMs dtMs = now - state->lastUpdateTime;
Number step = (Number)dtMs / 1000. * rate;
if (target > position) {
position = min(target, position + step);
} else {
position = max(target, position - step);
}
emitValue<output_OUT>(ctx, position);
state->lastUpdateTime = now;
}
NOTE: UPD pin is not intended to be used…it is only to allow the node to update without having to add an alarm so that code copied from fade node would work with minimal changes. All I did was add 2 sections of code for the additional pins:
if (isInputDirty<input_SET>(ctx)) {
Number start = !getValue<input_TARG>(ctx);
emitValue<output_OUT>(ctx, start);
emitValue<output_ACT>(ctx, true);
}
if (getValue<output_ACT>(ctx)) {
emitValue<output_DONE>(ctx, true);
emitValue<output_ACT>(ctx, false);
}
The tricky part was realizing I needed an additional pragma line at the top to enable the isInputDirty check for the SET pin:
#pragma XOD evaluate_on_pin enable input_SET
NOTE: Normally this would not work for numbers:
Number start = !getValue<input_TARG>(ctx);
We can only use it here because the TARG and start will always be 0 or 1. “!” is logical “not”. 0 is false and 1 is true, so the “!” of 0 is 1, and the “!” of 1 is 0, so this provides a shortcut to determine the “opposite” number we need to start from.