Skip to content

Soft Takeover: More accurately differentiate between jumps and fader movements #485

Closed
@mdmayfield

Description

@mdmayfield

Currently when using Pick Up or Catch Up modes, working with absolute physical controls (faders, knobs) fast movements can fail to register, and parameter jumps can happen too often.

The trouble is:

  • When Jump Max is set to a very low value (say 1%), the fader/knob must be moved very slowly, or else the software target will stop responding due to the distance between two consecutive CC messages exceeding Jump Max; but
  • When Jump Max is set to a value high enough to allow for faster fader/knob movements (say 20%), when the physical control is out of sync from the target value, approaching the target value from either direction will cause an unpleasant (in this case 20%) jump in the opposite direction as it comes close.

After thinking this through at length, I believe I have a good solution. Unfortunately I'm completely unfamiliar with Rust as of this writing; otherwise I would make a real pull request.

My suggested approach to resolve this can probably be best explained in pseudocode (pardon the sloppy half-C, half-JavaScript, half-who-knows-what syntax. It probably has some bugs as I haven't actually implemented this yet). Hopefully this makes sense:

Edit: rewrote this completely, still in a C-like pseudocode, after much additional thought and looking at what the Mixxx project does in a similar situation. This would be a helper function, called as each controller data value is received.

bool isInSync(
		float jumpMax,
		float jumpMin,
		unsigned int prevTimestamp,
		float prevValue,
		unsigned int currTimestamp,
		float currValue,
		float targetValue,
		bool inSync) {

	unsigned int elapsedTime = currTimestamp - prevTimestamp;
	float currDistance = currValue - targetValue;
	float prevDistance = prevValue - targetValue;
	bool sameSide = (currDistance < 0 && prevDistance < 0) || (currDistance > 0 && prevDistance > 0);
	bool approachingTarget = sameSide && fabs(currDistance) < fabs(prevDistance);

	// No matter what, if we're less than JumpMin, don't sync
	if fabs(currDistance) < jumpMin return False;

	// If we were in sync less than TIMEOUT ms ago, stay in sync
	if inSync && elapsedTime <= TIMEOUT return True;

	// New takeover logic. Goals:
	//
	// 1) Avoid awkward "backwards" jumps when approaching the target.
	// 2) Ensure that we pick up fast movements that *pass through* the target, even if
	//    the individual controller values never actually fall inside the Jump Max range.
	// 3) Enable a typical "set and forget" Jump Max of 3% or less without sync loss.
	//
	// Approaching is detected by either a quick movement from outside Jump Max to
	// inside it (both on the same side of the target), OR a slow movement (whose step
	// timings may exceed TIMEOUT due to coarse 0-127 resolution) within Jump Max.
	//
	// Inspired by controllers/softtakeover.cpp in the Mixxx DJ project.

	if abs(currDistance) <= jumpMax {
		if approachingTarget && (elapsedTime < TIMEOUT || fabs(prevDistance) < jumpMax) {
			return False;
		} else {
			return True;
		}
	} else if !sameSide && elapsedTime < TIMEOUT {
			return True;
		}
	}

	return False;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions