SoftSwitch
SoftSwitch cross-fades between two sets of signals and their first and second derivatives, and can work out its own fade time from velocity, acceleration and jerk limits instead of taking a duration.10 minute read
SoftSwitch cross-fades between two sets of signals and their first and
second derivatives, and can work out its own fade time from velocity,
acceleration and jerk limits instead of taking a duration.
Use it for a handover where the output’s derivatives matter — where a feedforward path or a compliance model is consuming them, and a fade that respects a speed limit is worth more than a fade of a fixed length.
flowchart LR
i1(["input1, input1Dot, input1DDot"]) --> B["SoftSwitch"]
i2(["input2, input2Dot, input2DDot"]) --> B
i3(["toggle"]) --> B
i4(["doInstantToggle"]) --> B
i5(["timeScaleFactor"]) --> B
i6(["timeScaleFactorDot"]) --> B
B --> o1(["output, outputDot, outputDDot"])
B --> o2(["isOn"])
B --> o3(["isOff"])
B --> o4(["isDone"])
B --> o5(["faderValue"])
The derivatives are correct here.
outputDotreally is the rate of change ofoutput, right through the fade, including the part that comes from the blend itself moving.SimpleSwitch3Ddoes not do this — if you need derivatives you can trust during a handover, this is the block.
Two ways to set the timing. With
maxConstraintsEnablefalse you give a duration. With it true you give velocity, acceleration and jerk limits and the block computes the duration so the output respects them.
In limit mode, the timing is worked out at the moment you flip the toggle, from the gap between the inputs at that instant. If the inputs move apart afterwards, the limits will be exceeded.
Signals
Inputs
| Path | Unit | Range | Description |
|---|---|---|---|
input1 |
signal unit | unbounded | The set selected when toggle is false. Where the block starts. |
input1Dot |
signal unit per second | unbounded | Its rate of change. You supply this; it is used to make outputDot correct. |
input1DDot |
signal unit per second² | unbounded | Its acceleration. |
input2 |
signal unit | unbounded | The set 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 set the output travels to. A level, not a pulse. Flipping it back mid-fade reverses smoothly, with no step in any derivative. |
doInstantToggle |
- | true or false | While true, all three outputs snap to the selected set. A level — leave it true and the block never fades again. |
timeScaleFactor |
- | any | Stretches the fade’s clock. 0 freezes it with nothing lost. A negative value is used as its magnitude; the fade does not run backwards. |
timeScaleFactorDot |
1/s | any | How fast timeScaleFactor is itself changing. Set this whenever you ramp the scale factor, or outputDDot will be wrong. |
If the derivative inputs are not really the derivatives of the value inputs, the outputs will be a consistent blend of inconsistent data. Nothing checks this.
Outputs
| Path | Unit | Description |
|---|---|---|
output |
signal unit | The blended value. |
outputDot |
signal unit per second | Its true rate of change, including the blend’s own contribution. |
outputDDot |
signal unit per second² | Its true acceleration. |
isOn |
- | The fade to input2 is complete. Not just started. |
isOff |
- | The fade to input1 is complete. Starts true. |
isDone |
- | No fade is in progress, in either direction. |
faderValue |
- | How far through the fade, 0 at input1 and 1 at input2. This is the shaped fraction, so scaling another signal by it matches this switch’s own profile. |
Parameters
| Path | Unit | Default | Range | Effect |
|---|---|---|---|---|
maxConstraintsEnable |
- | false | - | False: the fade takes fadeInTime or fadeOutTime. True: the duration is derived from the limit parameters below. |
switchType |
- | 0 | 0 or 1 | 0 quintic, 1 jerk-limited. See the table below. Changing it during a fade produces a kink — change it only when isDone is true. |
fadeInTime |
s | 5.0 | above 0 | Fade duration to input2, when limits are off. A value under one task period makes the switch snap. |
fadeOutTime |
s | 5.0 | above 0 | Fade duration back to input1, when limits are off. |
delayBeforeFadeIn |
s | 0.0 | 0 upward | Wait before a fade to input2 starts. Applies in both timing modes. |
delayBeforeFadeOut |
s | 0.0 | 0 upward | Wait before a fade back starts. |
fadeInMaxVel |
signal unit per second | zeros | 0 upward | Per channel. The most the output may move per second while fading in. 0 means this limit is not applied. |
fadeInMaxAcc |
signal unit per second² | zeros | 0 upward | Per channel. 0 means not applied. |
fadeInMaxJerk |
signal unit per second³ | zeros | 0 upward | Per channel. 0 means not applied. |
fadeOutMaxVel |
signal unit per second | zeros | 0 upward | Per channel, for the fade back. |
fadeOutMaxAcc |
signal unit per second² | zeros | 0 upward | Per channel. |
fadeOutMaxJerk |
signal unit per second³ | zeros | 0 upward | Per channel. |
fadeInJerkFactor |
- | 0.333 | 0 to 0.5 | Shapes the jerk-limited profile. Ignored when maxConstraintsEnable is true — the block computes its own. |
fadeInAccFactor |
- | 0.429 | 0 to 0.5 | Same, and same caveat. |
fadeOutJerkFactor |
- | 0.333 | 0 to 0.5 | Same. |
fadeOutAccFactor |
- | 0.429 | 0 to 0.5 | Same. |
All are persistent and survive a controller restart. Negative limits and times are used as their magnitude. No parameters exist below this block.
switchType |
What the machine does | Limits that apply |
|---|---|---|
| 0 quintic | Value, velocity and acceleration all start and end at zero. The smoothest shape | Any of the three that is non-zero |
| 1 jerk-limited | Reaches its acceleration and velocity plateaus and holds them, so it covers ground faster | A channel is ignored unless both its velocity and acceleration limits are set. A zero jerk limit degenerates the profile to a second-order one |
Setup
-
Connect both sets of three signals. All six must have the same number of channels.
-
Leave
togglefalse. Confirmoutputmatchesinput1andisOffis true. -
Start in time mode: leave
maxConstraintsEnablefalse and setfadeInTimeandfadeOutTimeto a second or two. -
Set
toggletrue and watch all three outputs travel to theinput2set.Step 4 hands the machine over to whatever
input2is carrying, at whatever rate the gap and the fade time imply. In time mode nothing limits that rate. Check the gap first. -
Confirm
isOnandisDonego true and the outputs match theinput2set. -
Now try limit mode: set
fadeInMaxVelper channel, setmaxConstraintsEnabletrue, and toggle again. -
Confirm the fade actually got slower or faster. If nothing changed, the solve found no limits to apply and silently fell back to
fadeInTime— nothing reports this.
Tuning
- Decide which mode you want. Limits are usually the right answer for a handover on a real axis, because they bound what the machine does rather than how long it takes.
- In limit mode, set the velocity limit first, per channel, in the signal’s own units. Leave acceleration and jerk at 0 until you need them.
- Verify the mode took effect by comparing the fade duration against the time-mode default. A silent fallback looks exactly like a correctly derived five-second fade.
- Remember the slowest channel sets the duration for all of them. One channel with a much smaller limit will slow the whole switch.
- With the jerk-limited shape, set both a velocity and an acceleration limit per channel or that channel is skipped entirely.
- Close the gap between the two input sets before switching wherever you can. In limit mode this shortens the fade automatically.
- Use
timeScaleFactorfor an override, and settimeScaleFactorDotwhile you ramp it oroutputDDotwill be wrong for the duration of the ramp.
In limit mode the duration is whatever it needs to be. In time mode the duration is fixed and the velocity is whatever it turns out to be.
| Symptom | Cause | Action |
|---|---|---|
| Setting limits changed nothing | The solve found nothing to constrain and fell back to the fade times, silently | Check every limit is non-zero on at least one channel |
| Limits are set but ignored with the jerk-limited shape | That shape skips any channel without both a velocity and an acceleration limit | Set both, per channel |
| The fade was slower than one channel’s limit implies | Expected: the slowest channel sets the duration for all | Raise that channel’s limits |
| The output exceeded the limit I set | The inputs moved apart after the toggle; the duration was fixed at the moment you flipped it | Close the gap before switching |
fadeInJerkFactor has no effect |
Expected: the factors are computed for you in limit mode | Turn limits off to use them |
| A kink appeared mid-fade | switchType was changed while a fade was running |
Change it only when isDone is true |
| The output moved very fast in time mode | Expected: time mode does not limit the rate | Use limit mode |
| The switch snapped instead of fading | A fade time under one task period | Lengthen it |
| The outputs stepped instead of fading | doInstantToggle is true |
Set it false — it is a level, not a pulse |
| The fade froze part-way | timeScaleFactor is 0 |
Set it above 0. Nothing was lost |
A negative timeScaleFactor did not reverse the fade |
Expected: the magnitude is used | Use the toggle to reverse |
outputDDot was wrong while I changed the override |
timeScaleFactorDot was left at 0 |
Set it while ramping |
isOn and isOff are both false mid-fade |
Expected: both require the fade to have completed | Use faderValue for progress |
| The derivative outputs never made sense | The derivative inputs may not match the value inputs | Nothing checks this — verify upstream |
| The machine moved when I flipped the toggle | Expected: the two input sets held different values | Match them first |
| Everything became invalid and stayed that way | An input that is not a number is not caught | Fix the upstream signal |
A starting point for a limit-based handover on a single axis:
maxConstraintsEnable = true
switchType = 0
fadeInMaxVel = 0.5
fadeInMaxAcc = 5.0
fadeOutMaxVel = 1.0
fadeOutMaxAcc = 10.0
fadeInTime = 5.0
fadeOutTime = 5.0
fadeInTime and fadeOutTime are still worth setting sensibly — they are what
you fall back to if the limits do not apply.
Limits and errors
| Limit | Set by | What happens | Reported |
|---|---|---|---|
| Derivative consistency | Fixed | outputDot and outputDDot are the true derivatives of output, through the fade |
Not applicable |
| Timing mode | maxConstraintsEnable |
Limits derive the duration; otherwise the fade times do | Not reported |
| Limit solve failure | Nothing | If no limit constrains anything, the block silently uses fadeInTime/fadeOutTime instead |
Not reported |
| Limit validity window | Fixed | The duration is solved when the toggle changes, from the gap at that moment. Later input movement is not accounted for | Not reported |
| Limits per channel | The limit arrays | Each channel respects its own; the slowest sets the duration for all | Not reported |
| Zero limits | Checked | A zero limit is skipped, not treated as zero | Not reported |
| Jerk-limited shape | Fixed | A channel without both a velocity and an acceleration limit is ignored. A zero jerk limit degenerates the profile to second order | Not reported |
| Factor parameters | Fixed | The four factors are ignored in limit mode | Not reported |
| Output rate in time mode | Nothing | Not limited at all | Not reported |
| Sub-cycle fades | Checked | A fade time under one task period makes the switch snap | isDone stays true |
| Limit and time signs | Checked | Negatives are used as their magnitude | Read the values back |
switchType mid-fade |
Nothing | Not guarded. An in-flight fade changes shape and keeps its old timing | Not reported |
timeScaleFactor |
Partly checked | Negatives are negated. 0 freezes the fade with nothing lost | Not reported |
| Values that are not numbers | Nothing | Not checked anywhere | Not reported |
| Startup | Fixed | All three outputs start on the input1 set; isOff and isDone start true, and any fade in flight is cleared |
isDone |
| Channel count | Fixed at build time | All inputs, outputs and limit arrays share one channel count | Not reported |
This block logs nothing, ever — unlike the other switches, it has no fade time ceiling and no warnings. Every condition above shows as a value on a trace, or not at all.
Verified against motorcortex-control3 3.30.0 (bc348fd).