Bug in ControlSpec .map and .unmap when using CurveWarp after range change

Hi all.

If a ControlSpec is created with a CurveWap and then its minval or maxval are changed, methods map and unmap return incorrect values.

Here’s some example code.
For both map and unmap examples, value b is incorrect.

// .map examples

a = ControlSpec(0, 8, 2).map(0.5);
// -> 2.15153137096

b = ControlSpec(0, 8, 2).maxval_(4).map(0.5);
// -> 2.15153137096
// Error - should be same as c

c = ControlSpec(0, 4, 2).map(0.5);
// -> 1.07576568548

// compare specs
ControlSpec(0, 8, 2).maxval_(4) == ControlSpec(0, 4, 2);
// -> true
// returns true yet they give different results

// .unmap examples

a = ControlSpec(0, 8, 2).unmap(4);
// -> 0.71689041524151

b = ControlSpec(0, 8, 2).minval_(2).unmap(4);
// -> 0.71689041524151
// Error -  should be same as c

c = ControlSpec(2, 8, 2).unmap(4);
// -> 0.57046623877689

As a temporary hack, you can copy the ControlSpec to get it working properly:

// EZSlider example with odd behaviour due to ControlSpec
(
w = Window("test", Rect(50, 50, 500, 120));
w.addFlowLayout;
EZSlider(w, 450 @ 30, "OK",  ControlSpec(0, 4, 2));
EZSlider(w, 450 @ 30, "Not OK",  ControlSpec(0, 8, 2).maxval_(4));
EZSlider(w, 450 @ 30, "OK with copy",  ControlSpec(0, 8, 2).maxval_(4).copy);
w.front;
)

Looking at the CurveWarp source code, I think this happens because map and unmap use internal variables a, b, and grow which are set in the init method when a CurveWarp instance is created. These variable don’t seem to be updated when the ControlSpec minval and maxval are changed.
I’ve been using ControlSpec for years, but never noticed this before!
Should I make a bug report?

Best,
Paul

Or a PR if you reckon you have a solution!

J

One solution that seems to work is to add this method to CurveWarp:

	asWarp { arg inSpec;
		^this.copy.spec_(inSpec).init(this.asSpecifier)
	}

But I’m not sure if this is the best way.
I’d appreciate other people’s eyes on this.
Thanks,
Paul

I took a quick look – I agree that asWarp is the right place to intervene (short of a wholesale refactoring, which would be excessive).

My only suggestion would be to call ^this.class.new(inSpec, this.asSpecifier) instead – the intention is to make a new warp, and *new is the most direct way to do that (rather than copying and modifying the copy).

hjh

Many thanks. I’ll make an issue with suggested fix.
Best,
Paul