RobinSchmidt / RS-MET

Codebase for RS-MET products (Robin Schmidt's Music Engineering Tools)
Other
56 stars 6 forks source link

Allow typing in values outside of min/max slider range #43

Open elanhickler opened 7 years ago

elanhickler commented 7 years ago

For sliders, could you provide an option per slider to allow user to type in and accept a value that is outside the range of the slider?

RobinSchmidt commented 7 years ago

phew - that's actually totally against the rules. i need to think about the consequences...

elanhickler commented 7 years ago

robin, how about something simple: separate slider range "slidable range" and slider range "actual range"

We can have two ranges for sliders.

elanhickler commented 7 years ago

We need multiple ranges anyway, I eventually want to have a right click menu "extend range" option for some sliders where some power users may want extreme range while others will want "easy mode" ranges. You don't need to worry about this at the moment. Just something to think about.

RobinSchmidt commented 7 years ago

hmm...well - i have something like this in func-shaper. there you can adjust min/max values of the a,b,c,d sliders. do you think, such functionality should be encapsulated into a class? we could make a slider subclass with min/max textfield widgets

RobinSchmidt commented 7 years ago

or maybe not a subclass but one that contains a slider and 2 textfields

elanhickler commented 6 years ago

I just thought of something. We had a discussion on changing parameter ranges (in code) and that ruins presets, at GitLab I believe. What if you stored the ranges as part of the patch xml system? Then later you could also introduce the ability to change slider ranges. There's a lot of times I wish I could change parameter ranges for specific situations. If the user needs a larger range for convenience, it's... well... very convenient. This could be part of the slider right click menu.

RobinSchmidt commented 6 years ago

the main reason for not doing such things is that i don't want to bloat and clutter the preset files. my reasoning is that it's rather unlikely to change a parameter range after a product has been published. i, as a programmer, have the responsibility to think about the appropriate range beforehand. and if that should really be necessary to change it at some point, i can still resort to the preset versioning and explicit conversion (and then, only range increases are legal without potentially breaking patches). i also don't store parameter values, when they are equal to their default values - also for economy reasons

elanhickler commented 6 years ago

for me it's necessary as there's still missing features in the library that prevents me from using the ranges I'd like to use. If the preset could store ranges I can wait comfortably while those features are being added and change the ranges later.

No idea how to do version converting of presets.

RobinSchmidt commented 6 years ago

No idea how to do version converting of presets.

you retrieve the PatchFormat attribute from the xml, and compare the returned value to the AudioModule's patchFormatIndex member (that you can set in the constructor, if you need to upgrade the format). if the patch format in the xml is older than the member, make appropriate changes to the relevant xml-attibutes. look at PitchShifterAudioModule::convertXmlStateIfNecessary for an example (although, i don't really compare to the patchFormatIndex member there - i just look, if the returned value is equal to 0 and then i already know, what to do).

well, it's a bit messy.

i could provide hook-functions like storeParameterToXml/recallParameterFromXml which you could override and then you could store/recall min/max values there. ...i.e. make your own baseclass for all your AudioModules (subclass of my ModulatableAudioModule class)

but....i think, this will still mess up presets, when they are connected to meta-parameters

elanhickler commented 6 years ago

I'm no longer going to connect meta-parameters by default.

elanhickler commented 6 years ago

The problem I'm having right now is that I have a frequency knob with range -5000 to +5000. I want bipolar frequency and 0 frequency allowed. Problem is that the usual range of LFOs is between -10 and +10hz, difficult to get that with linear scaling, so I'd probably want to do exponential scaling, but you can't have negatives and zeros for expo. I propose pow3 scaling:

https://www.desmos.com/calculator/yhzdws7qfp

could you add it really fast?

Edit: Hmm, I'd prefer a bipolar exp scaling, something like if abs(value) is < 1.e-6, value is zero or if value is < -1.e-6, do inverted exp scaling. So that gives us an accuracy of 6 decimal places, where 0 is between -1.e-6 and +1.e-6.

here's bipolar expo scaling:

https://www.desmos.com/calculator/6cttsmpymr

or maybe (value^7)/20000 for bipolar-expo-like response

I wouldn't mind setting frequncy to range of 1.e-6 to 5000 for now with expo scaling if I could change it later to a bipolar slider and not mess up presets.

RobinSchmidt commented 6 years ago

i think, it's time to facilitate the use of custom mapping functions for parameters. a bipolar version of the exponential mapping is something quite particular and it needs at least one additional parameter to specify its behavior. it could be based on my linToExpWithOffset function (and the corresponding expToLinWithOffset inverse function - we always need two-way mapping). this specifies another "offset" parameter that determines the curve/slope at zero. you could use the regular 20...20000 mapping with an offset of -20 and get roughly the same exp-scaling behavior, but from 0 to 20000. and then that could be mirrored for negative values. however, i think, this is too much to pack into the parameter class. most of the time, it will be just unneeded bloat in the objects

RobinSchmidt commented 6 years ago

ok - my Parameter class supports now custom mapping functions. this was a sort of open-heart surgery of one of my most basic framework classes, so some care had to be taken and it will need some good testing. it has a new member function:

virtual void setMapper(rsParameterMapper* newMapper);

which you can use to pass an object of a subclass of rsParameterMapper into. your subclass must implement map and unmap for forward and backward mapping (unmap should be the inverse function of map, for example if map is exp then unmap should be log). however, you don't need to use this. the old ways (of selecting one of the enumerated scalings) still work - but what happens internally is that an appropriate mapper object is then created inside the Parameter object itself).

RobinSchmidt commented 6 years ago

so, now the mapping is completely flexible and you can have any mapping you want by implementing your own mapper subclass and configuring the parameter object with it.

...as for the bipolar exponential mapping: i have thought about this and i think now, the best (most natural, smooth) function for this would be something based on sinh. for example y = a * sinh(b*x) for some constants a,b chosen appropriately to cover the desired range and have the desired slope at the origin (the lower the value of b, the more linear it becomes). i think, this is the next task: working out the details of the sinh mapping. maybe it could be further flexibilized by using y = a * sinh(b*(x+c)) + d

elanhickler commented 6 years ago

no robin! I only understand pictures, you have to make desmos 😸

https://www.desmos.com/calculator/vpnilxqab0

this feels right to me.

elanhickler commented 6 years ago

OH WAIT A MINUTE, bipolar exponential FM might be f***ing amazing. Linear FM stays in tune, exponential FM does not, because lower values have less change in frequency unlike linear where you get the same amount of change in frequency whether you go higher or lower, but I've noticed expo FM having a really nice sound, distinct from linear FM, so I've wanted to get a "stable tuning" version of expo FM.

Wow, what if we did a crossfade from bipolar expo / bipolar linear mapper for use as an FM frequency mapper? That could make for some awesome sound synthesis.

RobinSchmidt commented 6 years ago

YES!!! that sounds like some interesting stuff to explore!

elanhickler commented 6 years ago

mapper rsParameterMapperSinh doesn't seem to work, slider just goes to 0 when moved.

image

also you gotta fix this bug:

image

you have your numscalings before CUSTOM which throws an error becaue you check to make sure you're not using a scaling that is more than numscalings.

also you need to have a constructor, default calls the subclass's constructor I'm pretty sure

image

also, could you make setShape allow for chaning a AND b?

RobinSchmidt commented 6 years ago

mapper rsParameterMapperSinh doesn't seem to work, slider just goes to 0 when moved.

yes, i know - i said that in the other thread. the patch to the slider class was actually simple. should work now.

as for "CUSTOM" being below NUM_SCALINGS: that's actually intentional: the custom scaling is not among the enumerated ones, so to speak. i think, i do this check only when you call setScaling - right? ...and you are not supposed to call that with "CUSTOM". the corresponding member will be set to CUSTOM, if you pass in a mapper object. does it throw an error anywhere? i haven't encountered any, so far.

btw.: when you use enumerated values, ALWAYS ALWAYS ALWAYS use the enumeration values. NEVER EVER EVER do things like: myParam->setScaling(2). i may change the order of the enumerations at some point, insert new items, etc. - this will then break your code

RobinSchmidt commented 6 years ago

the same goes also for filter types in rosic. i've seen you using such "magic numbers" before

RobinSchmidt commented 6 years ago

could you make setShape allow for chaning a AND b?

nope. a must be a function of b. they are coupled, otherwise, the min/max values come out wrong

RobinSchmidt commented 6 years ago

basically, you set b and max, and that fixes a, well, min/max actually, but currently, it requires that min = -max

elanhickler commented 6 years ago

how would I get this shape?: https://www.desmos.com/calculator/vpnilxqab0

RobinSchmidt commented 6 years ago

this exact function? well, you already know your b-value. it's one in this case. you want to know how to set max? just read off the value of the graph at x=1 - it's around 0.87. i probably could come up with an exact formula for the exact value but for what? i don't know what you are trying to achieve...

elanhickler commented 6 years ago

I'm going to use this curve for bipolar frequency and want to make an rsParameterMapper subclass, and need to setup the map and unmap functions.

(deleted next post)

RobinSchmidt commented 6 years ago

I'm going to use this curve for bipolar frequency and want to make an rsParameterMapper subclass, and need to setup the map and unmap functions.

why do you want to make a subclass and not just use the rsParameterMapperSinh class? just set your min/max frequency value, like to -20000 to + 20000 or whatever and then set up your b (shape) value in a way which nicely balances the precision around zero with the precision at +-20k. this is exactly what this is made for. like

freqParam->setMapper(new rsParameterMapperSinh(-20000, +20000, 2.0));

the last constructor parameter, here 2.0, is "b" and it sets the shape, i.e. determines the trade-off between low-freq and high-freq precision of the slider. there actually is nothing more to do than just choosing your min/max (symmetrically) and setting the shape-parameter b.

edit: ok - i also deleted the other posts related to xoxos osc

elanhickler commented 6 years ago

sooooo, I set the min max of the mapper, but that is NOT the min/max of the slider? It is simply the min/max of controlling curvature or someting? On the slider I want -5000 to +5000 for example.

RobinSchmidt commented 6 years ago

the min/max of the mapper is the min/max of the parameter is the min/max of the slider. you want a different range for the slider than the parameter has?

elanhickler commented 6 years ago

no, i just don't know, given your sinh mapper how to achieve this curve: https://www.desmos.com/calculator/vpnilxqab0

now, it's not like I MUST have this exact curve, but I thought it would be easy to try. No? Anyway, ill use your sinh mapper as is for now.

RobinSchmidt commented 6 years ago

i think, there's no much sense in wanting this exact curve. our exponential mappings for frequencies say, 20...20000, are also scaled (input and output wise) so as to hit the correct values 20 and 20000 at 0 and 1 (it's also some y = a exp (b x) actually). more meaningful might be to solve for a b-value, such that a middle frequency...say 1000Hz is at half of (the right part of) the slider range. this is the only thing, one could do, when the maximum (value at 1) is already fixed. ...so something that could make sense is to choose b in a way such that a sinh(b x) and the A exp(B x) of the exp-scaling most closely resemble each other (the coeffs are of course different, that's why capital letters for exp)

RobinSchmidt commented 6 years ago

...or wait...you are actually shifting your exp-function to the left...i think, this is different from what our exp-scaling does. it's an exp with shifted input....hmmm...

elanhickler commented 6 years ago

don't worry about it, I have no idea what I'm doing. 😄

RobinSchmidt commented 6 years ago

make sure that you choose your b-value wisely. later changing it will break the mapping to meta parameters (i.e. may break patches with connected metas and daw automations)...it's similar to trying to change a parameter range later

elanhickler commented 6 years ago

Yes, I found a good way to think of it, just ask what range is most important.

I want -5000 to +5000, but the important values are perhaps -10 to +10, so I choose a B value where the slope around -10 to +10 looks about linear.

I have a gain slider that is -8 to +8. -1 to +1 is the most important. So again, just make the curve about linear around there.

image

Edit: Or maybe what I mean is that there is a "square" between 0 and your important value. Edit: Uhh nevermind, that's not a good explanation. This way of thinking doesn't work for my gain parameter.

elanhickler commented 6 years ago

still hitting this:

jura_Parameter.cpp line 195 image

Even though I can get past this build error, I'm still not getting scaling it seems:

myparams.cpp line 113 image

elanhickler commented 6 years ago

Is it possible to create a parameter mapper that skips 0 and is integer numbers? So I need a slider that has a sequence like this:

-5 -4 -3 -2 -1 +1 +2 +3 +4 +5

I can try to attempt it once the sinh works.

RobinSchmidt commented 6 years ago

still hitting this

i need to know, which class/function this is. you cut the top off too early ;-)

RobinSchmidt commented 6 years ago

here's how you can see the actual mapping function in term of max (m) and b: https://www.desmos.com/calculator/pamgkqwry9 i chose a range -20000 to +20000 as would be appropriate for frequencies in the audible range. the slider value would go from -1 to 1 in this case. i'd probably look at the frequency value at x=0.5 and make sure that the graph goes through (0.5, fMid) where fMid is some frequency in the middle of the audible range, in this case. say, having 1kHz at x=0.5 would call for b=6 (pi times thumb)

RobinSchmidt commented 6 years ago

ok, i'm allowing to set Parameter::CUSTOM from client code now (have dragged the value before NUM_SCALINGS)

elanhickler commented 6 years ago

jura_Parameter.cpp line 195

myparams.cpp line 113

I thought setting to custom was what you had to do? not sure how else it'd work.

RobinSchmidt commented 6 years ago

as said above, actually the idea was that the user/client doesn't manually sets this option but it is only set internally as soon as you pass a mapper. but i didn't take into account that the constructor also requires this setting....you could actually have passed any value to the constructor, like LINEAR - it would later be updated in the object to "custom" when you pass the mapper anyway. but it would be weird/confusing to do it that way, so i have now just dragged the value up. ...it's still not very elegant, though

elanhickler commented 6 years ago

is it possible make a parameter mapper that skips 0? -5 -4 -3 -2 -1 +1 +2 +3 +4 +5

here's the linear equations:

double   map(double x) const override { return min + (max-min) * x; }
double unmap(double y) const override { return (y-min) / (max-min); }

if I copy that and add this to a new mapper class, would it work?:

x <= 0 ? x = x-1 : x
y >= 0 ? y = y+1 : y
RobinSchmidt commented 6 years ago

hmm..you need rounding somewhere...but then, how would the inverse function "unmap" look like? such a function is actually not invertible. we need inversion, because when client code calls setValue and a Meta is connected, we have to able to figure out the meta value (which is always in 0..1).

...hmm...don't know, maybe we could somehow avoid the requirement of two-way mapping. ...or you just map the rounded value back

elanhickler commented 6 years ago

still not getting a bipolar exponential feel to the sliders, seems to be bipolar linear.

RobinSchmidt commented 6 years ago

what's your b-value?

elanhickler commented 6 years ago

i've tried .01, .0075, .3, seems to make no difference

RobinSchmidt commented 6 years ago

look at this - such low b leads to almost linear scaling:

https://www.desmos.com/calculator/pamgkqwry9

you need something of the order of 1...10, you need moar beee!!!

elanhickler commented 6 years ago

uhhh... but ranges im dealing with could be -8 to 8, -5000 to 5000, now I'm confused as to how to use desmos to find a good b value. I thought I knew, now I have no idea

RobinSchmidt commented 6 years ago

this actually doesn't matter. the shape stays the same, just the whole graph gets scaled down by another "a" factor then

RobinSchmidt commented 6 years ago

this is what i meant when i said: a is fixed by the max-value, as soon as b (shape) is chosen

...if i'm not mistaken