RobinSchmidt / RS-MET

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

Built in smoother for parameters #66

Closed elanhickler closed 6 years ago

elanhickler commented 7 years ago

Could you put in a smoother for parameters?

The only non-obvious thing I would have to do is do some increment of the smoother in the processblock sample loop, easy. Would be pretty easy for you to implement as well.

Oh, and I'd have another place in code where I set the smoothing amount per parameter.

you have getValue, you have setValue. Add getSmoothedValue(), add incSmoothedValue(), or whatever.

I'm building my own system for smoothing for PrettyScope, already did it for Spiral Generator. Just annoying, waste of space in code!

elanhickler commented 6 years ago

...also what would you do, if the object is possibly element of several arrays at the same time?

just don't 👍

edit: really though, several arrays that need to add and remove one instance of an object? that's some crazy code.

RobinSchmidt commented 6 years ago

for example, my ModulationManager keeps an array of availableTargets and affectedTargets (where affectedTargets is a subset of availableTargets) and it totally makes sense - so in my per-sample function, i have to loop only over the targets that are actually affected (i.e. have incoming connections). i'm even considering to apply the same optimization to the sources (i don't do it so far because i assumed the number of sources would be rather low compared with the number of potential targets, so it may not be worth it - although, when i look at mushroom, i'm not so sure anymore about the validity of this assumption).

void ModulationManager::applyModulationsNoLock()
{
  int i;

  // compute output signals of all modulators:
  for(i = 0; i < size(availableSources); i++)
    availableSources[i]->updateModulationValue();
    // maybe we should loop only over an array of "usedSources"?

  // initialize modulation target values with their unmodulated values:
  for(i = 0; i < size(affectedTargets); i++)
    affectedTargets[i]->initModulatedValue();

  // apply all modulations:
  for(i = 0; i < size(modulationConnections); i++)
    modulationConnections[i]->apply();

  // let the targets do whatever work they have to do with the modulated value (typically, 
  // call setter-callbacks):
  for(i = 0; i < size(affectedTargets); i++)
    affectedTargets[i]->doModulationUpdate();
}
elanhickler commented 6 years ago

although, when i look at mushroom, i'm not so sure anymore about the validity of this assumption).

I want to make it more modular where you can add/remove LFOs/ADSRs/Breakpoints from the GUI, so the available sources may start off as just a few until the user adds stuff. I want to go for a wireless modular synth.

elanhickler commented 6 years ago

please put a pause function on a smoother. I need to be able to pause the value from changing.

or I could just set smoothing ~speed~ amount to infinity for specific parameters when needed.

RobinSchmidt commented 6 years ago

oh! what is that good for?

elanhickler commented 6 years ago

for pausing PrettyScope. You click on the canvas to draw something, the x/y offset starts moving, you release mouse, the x/y needs to stop moving. This is "mouse auto pause mode" so it makes it easier to draw stuff.

RobinSchmidt commented 6 years ago

so, you mean, you need it to stop immediately on releasing the mouse instead of keep moving and slowing down for some further fractions of a second?

elanhickler commented 6 years ago

huh? "instead of moving and slowing down" ? yeah, it has to stop changing value... but that's for me to worry about, all I need is to be able to change the speed of the smoother... you have a smoothing speed per smoother, right?

RobinSchmidt commented 6 years ago

yes - actually, per smoothing-target and the smoother then takes over the value from the target when the target gets assigned. but that's technical details

RobinSchmidt commented 6 years ago

okayyyy....the smoother is operational. i tested it in Chainer with FuncShaper. you can try it there with the dc-offset value. i also tested to assign it to a meta (and drive to the same meta) and that seems to work as well. modulation also.

you need to create your parameters as SmoothableParameter objects now. it's a subclass of ModulatableParameter. so currently, i have the rather weird inheritance hierarchy:

Parameter < MetaControlledParameter < ModulatableParameter < SmoothableParameter

although it seems to work, i want to try to change it into:

Parameter < SmoothableParameter < MetaControlledParameter < ModulatableParameter

we'll see how that works out. when i'm done with that, you can change your code back to creating ModulatableParameter objects. but if you want to try and use it right now, create SmoothableParameters

RobinSchmidt commented 6 years ago

btw. it will also get invoked when you switch presets. i guess, when setting up a rather long smoothing time, we'll get a preset-morph when switching presets. ...but that will almost certainly create a huge cpu spike. we may consider to temporarily turn smoothing off on preset switch. ...i need to include some facilities for that...

RobinSchmidt commented 6 years ago

oh - wait - now it's not working anymore...wtf?

edit: ok...i see...it works only when no meta is attached. when a meta is attached, it works when changing the meta, but not when changing the slider on the gui..but why did it seem to work at first? weird..

elanhickler commented 6 years ago

Yes, I want to use it immediately, I don't care if I have to change code later, I need to release Mushroom Generator ASAP.

But looks like I'll need to wait until you solve the meta attach issue.

RobinSchmidt commented 6 years ago

i think, i first need to change my inheritance hierarchy and see how it will go then.

RobinSchmidt commented 6 years ago

ok, it seems to work. you can keep your code as is - ModulatableParameter is now the outermost class and includes smoothing and meta-control. but for simpler plugins that don't use the mod-system, you can just create MetaControlledParameters to avoid the mod-system overhead

RobinSchmidt commented 6 years ago

oh - i have no thread-safety measures in place yet. ...will add that next

elanhickler commented 6 years ago

quick description of how to use?

How do I turn off smoothing for certain sliders?

edit: oh I guess i can put smoothing to 0... buuut then how do I pause the smoother? give a time of DOUBLE_MAX? probably not ideal though.

elanhickler commented 6 years ago
elanhickler commented 6 years ago

And aren't you excited to employ the smoothing manager for morphable presets?

edit: ok

image

just don't add it to the smoothing manager.

elanhickler commented 6 years ago

do I need to create my own smoothing manager like for modulation?

RobinSchmidt commented 6 years ago

quick description of how to use?

just look at processBlock of AudioModuleChain (that's the awkward class name of the Chainer). it's very similar to your smoother

And aren't you excited to employ the smoothing manager for morphable presets?

it might be fun to noodle around with but ultimately probably impractical for release due to the high cpu-load during the morph. if several dozens of parameters are simultaneously smoothing and each of them triggers (more or less heavy) calculations in the dsp engine, you can imagine how that drains on the cpu

How do I turn off smoothing for certain sliders? edit: oh I guess i can put smoothing to 0

exactly

Smoother Manager needs a "set smoothing amount for all sliders" function

hmm...ok, i think, i can add an option to override the individual setting in the smoothing-target

do I need to create my own smoothing manager like for modulation?

what? why?

elanhickler commented 6 years ago

thinking about it, if I need to disable smoothing for certain parameters and implement my own pause functionality, I won't be able to use the smoother manager's "set all sliders speed".

RobinSchmidt commented 6 years ago

i could perhaps provide a global switch for turning smoothing off. might be needed anyway to disable it temporarily during preset recall for reasons stated above. ...but yeah, if you need some very special functionality that is relevant only to PrettyScope and unlikely to be useful anywhere else, it's probably best to do it in a subclass

elanhickler commented 6 years ago

pause functionality... preset morphing... disabling smoother for certain parameters... I need to think more about all of this... let's start a preset morphing discussion for fun.

For now, please get on adding grid settings saving to breakpoint module.

Once that's done, I still need work on the phaselocking autotuner.

I also need you to perhaps work on the multiband compressor, I'll fund you with the money coming from that person / project.

There's a lot to do still!!!!!!

RobinSchmidt commented 6 years ago

....perhaps the smoother could have an undersampling function to get the cpu load down to managable levels, such that it doesn't update parameters every sample but only every N samples, where N is a user-adjustable undersampling factor.

elanhickler commented 6 years ago

undersampling... that reminds me. I'm also doing some SERIOUS code refactoring for prettyscope, I very badly want an upsampling method for it, it would improve the visuals by a factor of 10.

RobinSchmidt commented 6 years ago

hmm...yeah - as said, in PhaseScope, i would probably try to replace the connecting lines with cubic splines but such a thing is not so easy to realize in PrettyScope, because OpenGL has no spline primitive, so at the end, the splines would have to be approximated by a sequence of lines anyway. within my own c++ pixel-code, i could add such a spline-drawing primitive

elanhickler commented 6 years ago

right, wouldn't we just upsample the audio buffer? the audio buffer is what creates the x/y points.

RobinSchmidt commented 6 years ago

you could do that - but i'd certainly not recommend using an elliptic filter for the upsampling in this case (unless you like to see spurious gibbs ripples in the plot). you want something with good time-domain performance, i.e. bessel or maybe gaussian. the upsampled signal would not necessarily go through the original sample-points then anymore, though. with splines, it would.

elanhickler commented 6 years ago

I don't understand the issue. So.............. what needs to be done? Do we need an openGL expert to make splines instead of lines?

elanhickler commented 6 years ago

This is why I posted dood.al source code. https://gitlab.com/Hickler/Soundemote/issues/58

RobinSchmidt commented 6 years ago

does that use splines? ...ahh - ok - i see. it seems to use a lanczos filter (FIR...seems to be some form of a windowed sinc). so it's basically sinc interpolation

elanhickler commented 6 years ago

ok i made some section links for easy navigation

https://gitlab.com/Hickler/Soundemote/issues/58

elanhickler commented 6 years ago

My value change callbacks are not being hit. It's not even a problem of smoothing. Just simply moving a slider doesn't trigger a callback. My breakpoint isn't being hit for that callback.

image

image

image

image

image

image

elanhickler commented 6 years ago

image

call value change callbacks is false when i move a slider???

RobinSchmidt commented 6 years ago

hmm...i guess, you are coming from here?:

void rsSmoothableParameter::setValue(double newValue, bool sendNotification, bool callCallbacks)
{
  if(smoothingTime == 0.0 || smoothingManager == nullptr)
    Parameter::setValue(newValue, sendNotification, callCallbacks);
  else
  {
    double oldValue = getValue();
    Parameter::setValue(newValue, sendNotification, false);
    smoothingManager->addSmootherFor(this, newValue, oldValue);
  }
}

yes, when the baseclass method is invoked, i pass "false" to the "callCallbacks" parameter because the callback call is deferred to the "updatedSmoothedValues" call which is supposed to be called right before the next sample is supposed to be produced

elanhickler commented 6 years ago

yes image

so why it no work?

RobinSchmidt commented 6 years ago

i'll have a look at it in the debugger myself tomorrow

elanhickler commented 6 years ago

did you forget that I am using modulatable parameter 2 for std::function?

elanhickler commented 6 years ago

Assigning modulation to a parameter, even if that modulation amount is 0, will allow slider to change value, however it is not smoothed.

also, smoothingManager.needsSmoothing(); is never true

RobinSchmidt commented 6 years ago

ok, i fixed some bug. you created your own smoothing manager, but that's not how it's supposed to work. the smoothing manager exists once in the AudioPlugin object and is accessible globally by all AudioModules via a pointer (defined in baseclass AudioModule). ...so now the parameter setValue... functions get called...but it seems not being smoothed. but maybe that's now easier for you to figure out.

it's very similar to how the meta-stuff is handled - you don't create your own meta-manager either. ..but admittedly, that may not have been obvious

elanhickler commented 6 years ago

no idea why parameters are not smoothed.

what else do you have to do to make smoothing work?

im lost.

elanhickler commented 6 years ago

is smoothing speed 0 by default?

RobinSchmidt commented 6 years ago

oh - i see in the debugger that smoothingTime is set to 0.01. seems like you are setting that somewhere? but that's in milliseconds!

i also see that the smootherPool has 399 array-elements - but you have only 58 parameters - something seems wrong about the initialization. expected would be, that when the parameters are intitialized (or a preset is loaded), that as many smoothers are created as there are parameters. i say expected - but that's probably not desired. i think, i should switch off smoothing on initialization and preset load

elanhickler commented 6 years ago

If you're dividing smoothing time by 1000, that's kinda pointless.

RobinSchmidt commented 6 years ago

hmm...it's a matter of convention what units should be used for the parameters - seconds vs milliseconds in this case. i thought, milliseconds would be the more convenient unit here

elanhickler commented 6 years ago

I don't expect you to change this, but just think about it. Keep all time units in seconds throughout your library. To have multiple units of time is just an extra layer of complexity.

For example, I can make a slider display milliseconds or seconds, but the underlying value is always in seconds. Typing 1 vs .001 in code... well you don't know by looking at it whether it's a second of a millisecond or a millionth of a second.

Keeping time units the same means you never have to remember to specially multiply by 1000 for this or that object or parameter.

Smoothing works. Thanks.

elanhickler commented 6 years ago

If you want milliseconds then... you should name your function setSmoothingTimeMilliseconds... which will then make you not want to set things in milliseconds because of the extra long function name 😄

RobinSchmidt commented 6 years ago

hmmm....yeaaa....i sort of agree that consistency of units throughout a library is desirable. i was certainly thinking about that for rapt. in the rosic classes, i mostly use units of whatever i considered the most convenient unit to present to an end user on the gui - like milliseconds for compressors but seconds for reverb-times and so on. but in the jura framework i'm a bit more sloppy because i consider it mainly for personal use anyway. hmmm

elanhickler commented 6 years ago

Robin I wish I could just write all my own code and not use your library (GUI stuff I mean) so you wouldn't have to worry about me and my opinions, but........... I'm here to make money and I can't afford to write my own stuff due to time and lack of knowledge. If my business is successful, more money will be flowing your way.

If it were up to me, I would continue to pay for you to develop your interface with public use in mind and create an awesome one-stop framework to build a plugin from scratch.... well that's what you're doing right now! There's money potential here. You're doing it anyway, so why keep saying it's for personal use? You can keep saying that, but I think there's a lot of potential here for your business to grow.

I could be wrong.