SimpleSwitch3D
SimpleSwitch3D cross-fades between two sets of signals and their first and second derivatives.8 minute read
SimpleSwitch3D cross-fades between two sets of signals and their first and
second derivatives. A boolean picks which set you want and all three levels
travel there together over a configured time.
Use it where a feedforward path downstream needs position, velocity and acceleration together, and the source of all three has to change.
The 3D in the name means three derivative levels, not three axes. The channel count is set when the controller is built and defaults to one, the same as
SimpleSwitch.
flowchart LR
i1(["input1, input1Dot, input1DDot"]) --> B["SimpleSwitch3D"]
i2(["input2, input2Dot, input2DDot"]) --> B
i3(["toggle"]) --> B
i4(["doInstantToggle"]) --> B
B --> o1(["output, outputDot, outputDDot"])
B --> o2(["isOn"])
B --> o3(["isOff"])
B --> o4(["faderValue"])
During a fade,
outputDotis not the rate of change ofoutput. All three levels are blended with the same fraction, which leaves out the part of the derivative that comes from the blend itself moving. The error is zero before and after the fade, largest in the middle, and grows with the gap between the two sources. The figure under Tuning shows the size of it.Do not feed
outputDotto anything that must agree withoutputduring a handover. Differentiateoutputyourself, or keep the two sources close together while you switch.
Fading in and fading out are configured separately — their own times and their own shapes.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
input1 |
signal unit | unbounded | The value selected when toggle is false. This is where the block starts. |
input1Dot |
signal unit per second | unbounded | Its rate of change. You supply this — the block does not compute it. |
input1DDot |
signal unit per second² | unbounded | Its acceleration. Also supplied by you. |
input2 |
signal unit | unbounded | The value selected when toggle is true. |
input2Dot |
signal unit per second | unbounded | Its rate of change. |
input2DDot |
signal unit per second² | unbounded | Its acceleration. |
toggle |
- | true or false | Which input set the output should travel to. A level, not a pulse. Flipping it back mid-fade reverses from wherever it had got to. |
doInstantToggle |
- | true or false | While true, all three outputs snap to the selected set every cycle with no fade. Also a level. |
Nothing checks that the derivative inputs really are the derivatives of the value inputs. If you wire them inconsistently you get a consistent blend of inconsistent data.
Outputs
| Path | Unit | Description |
|---|---|---|
output |
signal unit | The blended value. |
outputDot |
signal unit per second | The blended rate. Not the rate of change of output during a fade — see the callout above. |
outputDDot |
signal unit per second² | The blended acceleration. Same caveat, larger. |
isOn |
- | The fade to input2 has completed. Goes true a few cycles early. |
isOff |
- | The fade to input1 has completed. Starts true. |
faderValue |
- | How far through the fade, 0 to 1. The straight-line fraction even when the shape is cosine. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
fadeInTime |
s | 5.0 | one task period to 10 | How long it takes to travel to input2. Values above 10 are silently reduced to 10, with a warning in the log. |
fadeOutTime |
s | 5.0 | one task period to 10 | How long it takes to travel back. Same ceiling. |
fadeInType |
- | 0 | 0 or 1 | Shape of the fade to input2. 0 straight line, 1 cosine. |
fadeOutType |
- | 0 | 0 or 1 | Shape of the fade back to input1. |
timeScaleFactor |
- | 1.0 | 0 upward | Speeds up or slows down the fade while it runs. 0 freezes it. Reset to 1 every time the controller starts. |
All are persistent and survive a controller restart, except that
timeScaleFactor is forced back to 1 at startup. No parameters exist below
this block.
fadeInType / fadeOutType |
What the machine does | Effect on the derivative error |
|---|---|---|
| 0 straight line | Constant blend rate, stopping dead at each end | The error is constant through the fade and steps at both ends |
| 1 cosine | The blend eases away from one set and into the other | The error is zero at both ends and peaks in the middle. Larger at its peak than the straight line, but smooth |
Setup
-
Connect both sets of three signals. All six must have the same number of channels.
-
Leave
togglefalse. Confirmoutputmatchesinput1andisOffis true. -
Set
fadeInTimeandfadeOutTime. Start with a second or two. -
Set both shapes to 1 unless you need the shortest transit.
-
Bring the two sources close together before switching — this both reduces the motion and reduces the derivative error to nearly nothing.
-
Set
toggletrue and watch all three outputs travel to theinput2set.Step 6 hands the machine over to whatever
input2is carrying, and the derivative outputs will be wrong for the duration of the fade. If anything downstream acts onoutputDot, check the size of the gap first. -
Confirm
isOngoes true and the outputs match theinput2set.
Tuning
- Trace both value inputs together before switching. The gap between them drives both the motion and the derivative error.
- Close that gap before switching wherever you can. A handover between two sources that already agree is nearly free.
- Set the fade time from the gap and what the machine can take.
- Lengthen the fade to reduce the derivative error: the error is proportional to the gap divided by the fade time, so doubling the time halves it.
- Use the cosine shape for a smooth blend, the straight line if you would rather the error be constant and predictable than smooth.
- Watch
faderValuefor the fade progress. With the cosine shape the outputs lag it early and lead it late. - Fade times are in seconds, so a task-rate change does not move them.
With both sources standing still the whole of outputDot is missing. In
general the shortfall is the gap between the sources times the blend’s own
rate.
| Symptom | Cause | Action |
|---|---|---|
| A feedforward path fought the machine during a handover | outputDot did not match output during the fade |
Differentiate output yourself, or close the gap before switching |
outputDot read zero while output was moving |
Expected: both sources were still, so the whole derivative is the blend’s own motion, which is not published | See the callout at the top |
| The derivative error is worse than I expected | The gap is large or the fade is short | Close the gap, or lengthen the fade |
outputDDot is further out than outputDot |
Expected: the second derivative is missing two terms, not one | Same actions |
| The machine moved when I flipped the toggle | Expected: the two value inputs held different values | Match them first, or lengthen the fade |
| The outputs stepped instead of fading | doInstantToggle is true |
Set it false — it is a level, not a pulse |
| The fade is shorter than I asked for | A time above 10 is reduced to 10 | Check the log; 10 seconds is the maximum |
| The fade time reads back as a tiny number | A value below one task period was raised to it | Write a longer time |
| The fade is slower or faster than the time I set | timeScaleFactor is not 1 |
Set it to 1 |
timeScaleFactor reverted after a restart |
Expected: it is forced to 1 at startup | Set it from your application after start |
| The fade froze part-way | timeScaleFactor is 0 |
Set it above 0 |
| The switch stuck at one end | timeScaleFactor is negative, which is not rejected |
Write a positive value |
| The switch never fades at all | The task period read as zero at startup, which is not guarded here | Restart the controller |
| A rate step at the start or end | The straight-line shape does that | Set the type to 1 |
isOn went true slightly early |
Expected: the completion test has a small tolerance | Watch faderValue |
A signal scaled by faderValue does not match |
Expected: it is the straight-line fraction | Apply the same cosine yourself |
| The derivative outputs never made sense | The derivative inputs may not be the derivatives of the value inputs | Nothing checks this — verify upstream |
| Everything became invalid and stayed that way | A fade time that is not a number is not caught | Rewrite the fade times |
A starting point for a handover between two profile sources:
fadeInTime = 2.0
fadeOutTime = 0.5
fadeInType = 1
fadeOutType = 1
timeScaleFactor = 1.0
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
| Derivative consistency | Nothing | outputDot and outputDDot do not match output during a fade. The shortfall is zero at both ends and peaks in the middle, proportional to the gap between the sources |
Not reported at all |
| Derivative inputs | Nothing | Not checked against the value inputs. Inconsistent inputs blend consistently | Not reported |
| Fade duration | fadeInTime, fadeOutTime |
All three levels reach the far end in exactly that time, divided by timeScaleFactor |
faderValue, isOn, isOff |
| Fade time range | Fixed at one task period to 10 s | Out-of-range values are clamped and written back | Logged as a warning, once per write |
| Output rate during a fade | Nothing | Not limited. Use SoftSwitch when the output’s derivatives must stay within limits |
Not reported |
timeScaleFactor |
Nothing | Not checked. 0 freezes; negative runs the fade backwards until it sticks | Not reported |
timeScaleFactor persistence |
Fixed | Forced to 1 at every controller start | Not reported |
| Task period | Not guarded here | A zero task period leaves the fade frozen. SimpleSwitch guards this; this block does not |
Not reported |
faderValue |
Fixed | Always the unshaped fraction | Not applicable |
doInstantToggle |
The input | A level. While true, every cycle snaps | faderValue reads 0 or 1 |
| Values that are not numbers | Nothing | Not caught by the range check | Not reported |
| Startup state | Fixed | All three outputs start on the input1 set; isOff starts true |
isOff |
| Channel count | Fixed at build time | All six inputs and three outputs share one channel count | Not reported |
The block logs a warning when a fade time is written outside its range. Every other condition above shows as a value on a trace, or not at all.
Verified against motorcortex-control3 3.30.0 (bc348fd).