Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

relative targets of absolute controllers (à la modulation matrix target amount) #457

Closed
jackmau opened this issue Oct 21, 2021 · 32 comments
Labels
enhancement New feature or request high priority

Comments

@jackmau
Copy link

jackmau commented Oct 21, 2021

Hi Benjamin,

First of all let me congratulate you on the abundant improvements you have brought to realearn in the last year (particularly useful for me are midi output control, double presses, mapping groups with their conditional activation, virtual mappings and group interaction).

This FR is close in some way to #273 and effectively a grossly simplified version of #274 (my use case is in fact very similar). Naturally forgive me if something similar is already possible in realearn, currently I am using my own jsfx to achieve a similar result, before realearn. (My custom midi JSFX have slimmed a lot with time since so many features have implemented directly in realearn!)

One of the few features I find missing in realearn is relative control, not intended in its strict midi sense (+1/-1 per each control message) but how it works in the classic modular synth environment (and in most synth plugin from NI massive onwards, including Surge, Vital, Pendulate ecc), where the controller input is relatively added or subtracted to its current target value. Note that this differs from a simple y +/-= x scenario, as when x = 0 we should be back to our original y. This is, for example, essential when using velocity or any other perfomance control, such as breath controllers, motion controllers, expression pedals ecc. in a live environment and changing presets/doing further knob changes at the same time. It is the way MW control is usually implemented in most hardware synth as well.

There are several ways I think this could be implemented, I am thinking to at least 3:

  1. as a separate mode, with realearn memorising the target internally
  2. giving the user the possibility to have minimum and maximum target values as realearn parameter/control targets
  3. giving the user the opportunity to use formulas using realearn parameters in setting minimum and maximum targets.

(1) sounds like a lot of work tbh, I think (2) would be the most user friendly, but I recognise (3) is more cohorent with realearn current structure. Naturally both (2) and (3) pose the problem of how not to update the parameter where the information is stored when the input is controlling our target.

@helgoboss
Copy link
Owner

As I understand you, you want some form of absolute-to-relative conversion. Correct? There's something similar in ReaLearn already: The takeover mode "Parallel". For example, it takes the movement of a mod wheel and applies them to the target parameter, but in a relative way. Since it's a "takeover mode", this behavior will come into play only if the hypothetical parameter jump is too big, meaning, you have to set "Jump Max" to a very small value.

I guess that's not exactly what you want because it's a feature tailored to avoid parameter jumps. But if I understand you correctly, it should be closely related. Could you try that and tell me if that's similar to what you want? And if yes, what would need to be different to satisfy your use case?

If it's in any way similar to that, I think way 1 (implementing this in ReaLearn itself) would be by far the easiest way to implement it. And I think also the most user-friendly?`Why do you think 2 sounds user friendly?

@vonglan
Copy link

vonglan commented Oct 21, 2021

Is #203 (basically: make absolute controller relative) related?

@helgoboss
Copy link
Owner

Right, it sounds a lot like #203. If yes, it would be nice if we could close it and continue discussion on #203.

@jackmau
Copy link
Author

jackmau commented Oct 22, 2021

controller input is relatively added or subtracted to its current target value.

As often happens when posting an issue I come back the day after seeing I have written exactly the opposite of what I meant. I am sorry for @helgoboss and @vonglan having had an hard time decyphering my words, and I am puzzled myself at looking how bad I managed to explain myself. Most of the confusion is generated by the fact that by current target value I actually meant last target value before the mapping started acting and by relative I meant actually absolute but scaled in the interval between the last target value before the mapping started acting and its maximum/minimum target value.

As I understand you, you want some form of absolute-to-relative conversion. Correct?

No, I think my issue description was misleading, output would still be absolute, but scaled relatively to last value of the target before the mapping acted. The meaning of relative here indicates relative to the last value, not the current one. For example, suppose you map the MW to a filter cutoff in an HW/SW synth. Typically the mapping would act either as a positive or negative amount on the current value of the target. Returning the MW to 0 would bring the cutoff back to its original value in the preset.

The takeover mode "Parallel". For example, it takes the movement of a mod wheel and applies them to the target parameter, but in a relative way. Since it's a "takeover mode", this behavior will come into play only if the hypothetical parameter jump is too big, meaning, you have to set "Jump Max" to a very small value.

Fair point and I have tested this as well, and it does indeed works well in the proper sense of relative (i.e. relative to the current value). But in my use case we are speaking of relative to the last value before this mapping has started acting.
Let's see with an example what I meant in point (3) of my issue.
Assume that p1 holds the last value of our target y before the mapping acted, and we have set y_max>p1. Assuming x is scaled to 1, EEL speaking our control would look like this: y = p1 + x * y_max, so that when x = 1 we reach y_max and when y=0 we are back to p1. No takeover would ever need to occur, as this is evidently meant for perfomance controls which would only affect a portion of the target values and the general assumption is that we are just adding/subtracting something that always starts and ends from 0 (MW/velocity ecc).

Is #203 (basically: make absolute controller relative) related?
An absolute-to-relative conversion, as in #203, which I studied before posting this, seems a different matter to me, as it deals with transforming absolute control input into relative control input. My request is for relative target min/max values (either via parameters or as a new mode), not _control values. In terms of FR I have seen this is effectively a subset of #274, which asked for something slightly different and with bidirectional behaviour, I am only pointing to one-directional behaviour in this FR (control is added or subtracted to the starting value, not both at the same time).

If it's in any way similar to that, I think way 1 (implementing this in ReaLearn itself) would be by far the easiest way to implement it. And I think also the most user-friendly?`Why do you think 2 sounds user friendly?

Sorry I was trying to put myself in your shoes and think about what would have been the quickest way of implementing something similar without having to do too much work. Ideally I agree that it would be nice to have just a button on the interface, with realearn storing the parameter target under the hood, but I supposed having parameters in the control transformation could have been easier, as they could be updated dynamically using conditional feedback.

@helgoboss
Copy link
Owner

Now I understand. Makes total sense. Something I would happily add. It's like considering "the initial target value" as the minimum target value (instead of zero).

ReaLearn already saves the initial value = the value that it had when the mapping was loaded. So we could use that as reference. Although it would probably be more ideal to also capture external (non-ReaLearn) changes that have been made to that value after mapping load time (as @vonglan requested in one ticket).

There's still an open question for me though: What about a pitch wheel? I guess users would expect that tearing the pitch wheel down would decrease the target value so that it goes below the initial value! Meaning that we can't simply say "initial target value = minimum target value". .

@vonglan
Copy link

vonglan commented Oct 24, 2021

And what if the starting position of the mod wheel (if you use that) is not at 0?
Should the target value jump as soon as the mapping is loaded?

I hope that this request can be integrated (turns out to be identical to) with a stripped-down #203 .
Without the switchability (I can sacrifice that - it is always possible with duplicating the mappings into different groups).

@helgoboss
Copy link
Owner

And what if the starting position of the mod wheel (if you use that) is not at 0? Should the target value jump as soon as the mapping is loaded?

I think this should be handled via jump/takeover, as with absolute mode "Normal". Then users have the choice. What do you think? And do you think, an "Absolute to relative" mode would still make sense, in addition? It would be similar, just without the scaling.

I hope that this request can be integrated (turns out to be identical to) with a stripped-down #203 . Without the switchability (I can sacrifice that - it is always possible with duplicating the mappings into different groups).

I think so, too.

@jackmau
Copy link
Author

jackmau commented Oct 25, 2021

ReaLearn already saves the initial value = the value that it had when the mapping was loaded. So we could use that as reference. Although it would probably be more ideal to also capture external (non-ReaLearn) changes that have been made to that value after mapping load time (as @vonglan requested in one ticket).

What do you mean by loaded? When the realearn instance is initiated or when the mapping is activated? Ideally I would like realearn to record the parameter when the mapping is activated, so that if I make changes to the target with another mapping, my MW/other controller is starting from the updated target.

There's still an open question for me though: What about a pitch wheel? I guess users would expect that tearing the pitch wheel down would decrease the target value so that it goes below the initial value! Meaning that we can't simply say "initial target value = minimum target value". .

Fair question, note that in my request I am only mentioning mono-directional control, either positive (where target=min) or negative (where target= max). PB is quite unique in being bi-directional, but we could have other controllers with similar behaviour (any spring based X/Y controller, or centered pot), since they will be centered, we can assume (target=mid), and then having separate min/max values. This seems to suggest a sort of dropdown/box selection, where you decide whether the last target value would represent the min, max or mid value. If it is the min only target Max control should be available, if it is the max, only min, if it is the mid value, both target could be available (or you could have just one and assume symmetric behaviour, a la LFO in a synth)

And what if the starting position of the mod wheel (if you use that) is not at 0? Should the target value jump as soon as the mapping is loaded?

I think this should be handled via jump/takeover, as with absolute mode "Normal". Then users have the choice. What do you think? And do you think, an "Absolute to relative" mode would still make sense, in addition? It would be similar, just without the scaling.

Fully agree on @helgoboss on this one, let's not overcomplicate things, jump/takeover modes already exist to deal with this type of problems. I agree that whether #203 looks at an additional takeover mode, my request is something that, logically in my mind, should be in the target section.

hope that this request can be integrated (turns out to be identical to) with a stripped-down #203 .
Without the switchability (I can sacrifice that - it is always possible with duplicating the mappings into different groups).

I am struggling to see the similarity with #203, as this behaviour is in my mind quite different from classic midi relative mode (where I have no guarantee to be back at my starting point if control is not properly scaled). @vonglan and @helgoboss, correct me if I am wrong but suppose y=64 and my MW(x) is at 0, if I translate 1:1 a
bsolute messages into relative by the time my MW is half way (x=64) y would already be at 127, if then I go to the end of the MW control (x=127) and I come back to the start point (x=0) my y should be at 0 as well, no? This extract from the OB-6 manual (Dave Smith) seems to suggest so:

Capture

Thus I think #203 should be kept separate from this as, from my understanding, it concerns more relative/jump/takeover modes, i.e. change to the input control values, whereas here is just targets which are interested, input control values are taken as absolute (but transposed to last target value and scaled according to min/max). We are speaking of a less generalised version of your #274 issue @vonglan.

@helgoboss
Copy link
Owner

What do you mean by loaded? When the realearn instance is initiated or when the mapping is activated? Ideally I would like realearn to record the parameter when the mapping is activated, so that if I make changes to the target with another mapping, my MW/other controller is starting from the updated target.

By "Mapping loaded" I mean when the mapping is added, loaded as part of a project/preset or adjusted by the user. You are adding a third possibility (in addition to mine and @vonglan's): To take the last value of the target which is not changed via this particular mapping. Probably makes the most sense here. @vonglan What do you think?

There's still an open question for me though: What about a pitch wheel? I guess users would expect that tearing the pitch wheel down would decrease the target value so that it goes below the initial value! Meaning that we can't simply say "initial target value = minimum target value". .

Fair question, note that in my request I am only mentioning mono-directional control, either positive (where target=min) or negative (where target= max). PB is quite unique in being bi-directional, but we could have other controllers with similar behaviour (any spring based X/Y controller, or centered pot), since they will be centered, we can assume (target=mid), and then having separate min/max values. This seems to suggest a sort of dropdown/box selection, where you decide whether the last target value would represent the min, max or mid value. If it is the min only target Max control should be available, if it is the max, only min, if it is the mid value, both target could be available (or you could have just one and assume symmetric behaviour, a la LFO in a synth)

Do we really need an additional dropdown menu? I think we could use "Reverse" in order to achieve negative direction. And I could add a new source character "Range with center position" - which will be implicitly set for a pitch bend source and can manually be chosen for CC sources.

Fully agree on @helgoboss on this one, let's not overcomplicate things, jump/takeover modes already exist to deal with this type of problems. I agree that whether #203 looks at an additional takeover mode, my request is something that, logically in my mind, should be in the target section.

#203 is not a takeover mode since its purpose is not primarily to prevent jumps, it just happens to work very similar to the takeover mode "Parallel".

The new mode will definitely go into the "Tuning" section, not the target section. The target section only contains things that are target-specific. This "Synth mode" (or however we are going to call it?) is potentially applicable to all kinds of targets that are not just on/off.

hope that this request can be integrated (turns out to be identical to) with a stripped-down #203 .
Without the switchability (I can sacrifice that - it is always possible with duplicating the mappings into different groups).

I am struggling to see the similarity with #203, as this behaviour is in my mind quite different from classic midi relative mode (where I have no guarantee to be back at my starting point if control is not properly scaled). @vonglan and @helgoboss, correct me if I am wrong but suppose y=64 and my MW(x) is at 0, if I translate 1:1 a bsolute messages into relative by the time my MW is half way (x=64) y would already be at 127, if then I go to the end of the MW control (x=127) and I come back to the start point (x=0) my y should be at 0 as well, no? This extract from the OB-6 manual (Dave Smith) seems to suggest so:

Capture

You are right, the scaling is the difference. But I think it will often achieve similar results.

Thus I think #203 should be kept separate from this as, from my understanding, it concerns more relative/jump/takeover modes, i.e. change to the input control values, whereas here is just targets which are interested, input control values are taken as absolute (but transposed to last target value and scaled according to min/max). We are speaking of a less generalised version of your #274 issue @vonglan.

Okay, let's keep it separate.

@helgoboss helgoboss added the enhancement New feature or request label Oct 25, 2021
@vonglan
Copy link

vonglan commented Oct 25, 2021

Thus I think #203 should be kept separate from this as, from my understanding, it concerns more relative/jump/takeover modes, i.e. change to the input control values, whereas here is just targets which are interested, input control values are taken as absolute (but transposed to last target value and scaled according to min/max). We are speaking of a less generalised version of your #274 issue @vonglan.

I agree. Thanks for sticking to your point.

(By the way, if we had the possibility to scale the input value with the EEL formula, we could even achieve the "multi-stage" control that I described in #274 , by using the conditional ? : operators, and separate mappings for each stage).

By "Mapping loaded" I mean when the mapping is added, loaded as part of a project/preset or adjusted by the user. You are adding a third possibility (in addition to mine and @vonglan's): To take the last value of the target which is not changed via this particular mapping. Probably makes the most sense here. @vonglan What do you think?

Sounds perfect to me.

Do we really need an additional dropdown menu? I think we could use "Reverse" in order to achieve negative direction. And I could add a new source character "Range with center position" - which will be implicitly set for a pitch bend source and can manually be chosen for CC sources.

Good idea. Would this new source character have any other effects/use cases other than the one of this feature request? What comes to my mind is that, when I created the SY presets (which did not find as big an audience as I expected ;-), I had to introduce EEL formulas for some cases to map from a bipolar envelope amount as source to a unipolar target (plus sometimes a "reverse" toggle) in the VST.

This "Synth mode" (or however we are going to call it?)

Hm, my first reaction is to call it "controller mode", as it is about controller elements (modulation wheel, pitch bend, aftertouch, breath controller and similar, but NOT normal knobs on a hardware synthesizer like "cutoff") acting in a "natural" and general way on a target value, without the need to adapt the mapping when you change (switch or adapt) the VST preset.
But then, "controller" is very, very much used in ReaLearn. "CC" or "Continuous Controller" is not much better (for example, because the ModWheel might send NRPN). "Hardware Controller" is also unsuitable, because a "cutoff" knob is also hardware.
Maybe the name should indicate the fact that the controller value is used as a relative or offset to the previous (or mapping-independent) target value. "target offset mode"?

@vonglan
Copy link

vonglan commented Oct 25, 2021

It's getting a bit late, but it seems to me that this

(By the way, if we had the possibility to scale the input value with the EEL formula, ...

would open a lot of possibilities, in a very elegant way:

@helgoboss
Copy link
Owner

@vonglan I'm completely lost ;) Need examples (input, formula, output, etc.).

@jackmau
Copy link
Author

jackmau commented Oct 26, 2021

Do we really need an additional dropdown menu? I think we could use "Reverse" in order to achieve negative direction. And I could add a new source character "Range with center position" - which will be implicitly set for a pitch bend source and can manually be chosen for CC sources

I trust your judgement on UI/implementation, whatever works for you I am happy with, and would still be less cumbersome than my EEL scripts with manually linked parameters.

Okay, let's keep it separate.

I agree. Thanks for sticking to your point.

Thanks to both of you to have the patience to go through my musings

when I created the SY presets (which did not find as big an audience as I expected ;-)

What are those SY preset you are mentioning here?

"controller mode", as it is about controller elements (modulation wheel, pitch bend, aftertouch, breath controller and similar, but NOT normal knobs on a hardware synthesizer like "cutoff")

I think Performance controller mode, sounds less confusing, as effectively we are dealing we are dealing mostly with perfomance controller (which is what I am currently using mostly realearn for)

@vonglan I'm completely lost ;) Need examples (input, formula, output, etc.).

I join @helgoboss request for more clarification, but if I understand well what you mean, you intend behing able to access the parameter we are scaling to (y_old) in the Control transformation section, isn't it? Or have a Scripted input that replaces the new sources altogether?

I remember when I read that in #274 and found it very puzzling at the time, but now after you mentioned multi-stage and non-linear control this makes more sense. You are effectively thinking to something akin to piece-wise control, (x from 0 to z controlling y between 0-64 and x within z to 127 controlling y from 64-127, with z definable as the old value of Y or something else) but that (correct me if I am wrong) would already be possible using two (or more) mappings.

@vonglan
Copy link

vonglan commented Oct 26, 2021

What are those SY preset you are mentioning here?

See here. If you are interested, you could do me a favor be reviving the Reaper discussion thread

I think Performance controller mode, sounds less confusing, as effectively we are dealing we are dealing mostly with perfomance controller (which is what I am currently using mostly realearn for)

My favorite (although a bit long, might be shorter in the UI) is: Performance Controller / Target Offset Mode (saying what the source is, and what it is used for).

@vonglan I'm completely lost ;) Need examples (input, formula, output, etc.).

I join @helgoboss request for more clarification, but if I understand well what you mean, you intend behing able to access the parameter we are scaling to (y_old) in the Control transformation section, isn't it? Or have a Scripted input that replaces the new sources altogether?

The basic idea is to offer an EEL formula to change the normalized source value.

  • So when the mod wheel is at 0%, the source CC is 0. This is normalized to 0.0.
  • If it is at 50%, the source CC is usually 64 (in a range of 0 to 127), so the normalized value is 0.5 (small rounding problem there, but let me continue)
  • If it is at maximum, the source CC is 127, and the normalized value is 1.0.
  • (If the Mod Wheel sends NRPN values from 0-2047, still the normalized value is in the range of 0.0 to 1.0.)

This value can then be changed with the EEL formula.

Afterwards, it is mapped to the target value as follows:

  • 0.0 leads to "last target value" (what this means exactly, should be discussed elsewhere)
  • 1.0 leads to maximum target value
  • -1.0 leads to minimum target value

The values in between are linearly interpolated. The values outside are adjusted to the border case (min/max).

Now the application of that to my "claims" from last night:

non-linear scaling

I can use y = x / 2 or y = 2 * x for simple scaling (just a more or less drastic effect as I turn the performance controller), or formulas like y = x*x for nonlinear scaling (small changes at first, then larger changes, etc.)

multi-stage or multi-target (like I described in #274 , by using the conditional ? : operators, and separate mappings for each stage)

Multi-Target can already be done without the EEL formula. But with the EEL formula, you can use

y = ( x > 0.6 ) ? ( x - 0.6 )*2.5 : 0
(I assume you are familiar with ? : being used for if-then-else) and use this with a target parameter like "distortion", while another mapping for the same performance controller just increases the cutoff slightly (see "scaling", above). So moving the modwheel up, at first the cutoff freq is increased, then in the last 40% of modwheel range, distortion sets in. In this way, different "stages" for the Performance Controller's range are implemented.

It could also be used instead of "new source character Range with center position" to accomodate pitchbend wheels.

If the pitchbend wheel sends CC 0-127, with 63.5 being the neutral value (there might be a small rounding problem there, because the CCs are always integer values). This will be normalized to the range 0.0 to 1.0. Then the EEL formula would be: y = ( x-0.5 ) * 2. Together with the mechanism described above (-1.0 being mapped to minimum target value), this would allow to use the pitchbend wheel in an intuitive way.

not mapping mod wheel 0 to "current value", but mod wheel at 20% (also described in Configure a general, relative target for each Performance Controller (ModWheel etc.) in all presets of all VSTs #274 ). So mod wheel at 0% maps to a (for example) somewhat lower cutoff than the "current value" in the preset, but when you turn up the mod wheel, it reaches the "current value" and then to higher cutoff values.

This would be possible using a formula like y = x - 0.2. Then, the 0% position of the mod wheel would yield a somewhat lower cutoff value than the "last target value". When the mod wheel is moved to about 20%, the cutoff is identical to its "last target value". For higher mod wheel positions, it is increased.

Concerning "last target value": @helgoboss said this might mean "the last target value before this particular mapping was activated". So without:

  • later manual movements via the VSTs UI
  • movements by other mappings

I think this might become complicated, because there can be clipping (calculated values bigger than target_max being reduced to target_max), and we still want to return to the neutral position when the mod wheel is in the neutral position again.

I hope my writing is better understandable now.

@helgoboss
Copy link
Owner

I think Performance controller mode, sounds less confusing, as effectively we are dealing we are dealing mostly with perfomance controller (which is what I am currently using mostly realearn for)

My favorite (although a bit long, might be shorter in the UI) is: Performance Controller / Target Offset Mode (saying what the source is, and what it is used for).

"Performance" sounds good. Maybe "Performance control" because a mapping is about a single control (control element) rather than a complete controller.

The basic idea is to offer an EEL formula to change the normalized source value.

  • So when the mod wheel is at 0%, the source CC is 0. This is normalized to 0.0.
  • If it is at 50%, the source CC is usually 64 (in a range of 0 to 127), so the normalized value is 0.5 (small rounding problem there, but let me continue)
  • If it is at maximum, the source CC is 127, and the normalized value is 1.0.
  • (If the Mod Wheel sends NRPN values from 0-2047, still the normalized value is in the range of 0.0 to 1.0.)

This value can then be changed with the EEL formula.

Ok, until here, no changes necessary. Because this is exactly how EEL control transformations work right now.

Afterwards, it is mapped to the target value as follows:

  • 0.0 leads to "last target value" (what this means exactly, should be discussed elsewhere)
  • 1.0 leads to maximum target value
  • -1.0 leads to minimum target value

The values in between are linearly interpolated. The values outside are adjusted to the border case (min/max).

Ok, this is different. At the moment, x is a value between 0.0 (0%) and 1.0 (100%). Negative values are not allowed.

I think you could achieve the same result if I make available the "last target value" as e.g. y_last. Then you have to do the linear interpolation yourself, but you can achieve the same result and the meaning of x and y is the same as always (I'd rather not like to change that as it will cause confusion).

Now the application of that to my "claims" from last night:

non-linear scaling

I can use y = x / 2 or y = 2 * x for simple scaling (just a more or less drastic effect as I turn the performance controller), or formulas like y = x*x for nonlinear scaling (small changes at first, then larger changes, etc.)

multi-stage or multi-target (like I described in #274 , by using the conditional ? : operators, and separate mappings for each stage)

Multi-Target can already be done without the EEL formula. But with the EEL formula, you can use

y = ( x > 0.6 ) ? ( x - 0.6 )*2.5 : 0 (I assume you are familiar with ? : being used for if-then-else) and use this with a target parameter like "distortion", while another mapping for the same performance controller just increases the cutoff slightly (see "scaling", above). So moving the modwheel up, at first the cutoff freq is increased, then in the last 40% of modwheel range, distortion sets in. In this way, different "stages" for the Performance Controller's range are implemented.

This is not new, it's already possible.

It could also be used instead of "new source character Range with center position" to accomodate pitchbend wheels.

If the pitchbend wheel sends CC 0-127, with 63.5 being the neutral value (there might be a small rounding problem there, because the CCs are always integer values). This will be normalized to the range 0.0 to 1.0. Then the EEL formula would be: y = ( x-0.5 ) * 2. Together with the mechanism described above (-1.0 being mapped to minimum target value), this would allow to use the pitchbend wheel in an intuitive way.

not mapping mod wheel 0 to "current value", but mod wheel at 20% (also described in Configure a general, relative target for each Performance Controller (ModWheel etc.) in all presets of all VSTs #274 ). So mod wheel at 0% maps to a (for example) somewhat lower cutoff than the "current value" in the preset, but when you turn up the mod wheel, it reaches the "current value" and then to higher cutoff values.

This would be possible using a formula like y = x - 0.2. Then, the 0% position of the mod wheel would yield a somewhat lower cutoff value than the "last target value". When the mod wheel is moved to about 20%, the cutoff is identical to its "last target value". For higher mod wheel positions, it is increased.

My current idea based on the input of both of you:

  • For "Normal" mode, also make y_last (or similarly named) available in EEL control transformation formulas (with this you can do all the stuff mentioned above).
  • Additionally, add a dedicated "Performance control" mode that considers the last target value as "Target Min" (if "Reverse" unticked) or "Target Max" (if "Reverse" ticked).
    • This mode is a convenience feature and easier to use if you don't need the amount of control that a EEL control transformation gives you.
    • This way of offering a dedicated convenience mode has tradition in ReaLearn. The "Toggle buttons" mode is also just a convenience. You could simulate it in "Normal" mode as well using the right EEL control transformation.

Concerning "last target value": @helgoboss said this might mean "the last target value before this particular mapping was activated". So without:

  • later manual movements via the VSTs UI
  • movements by other mappings

I think this might become complicated, because there can be clipping (calculated values bigger than target_max being reduced to target_max), and we still want to return to the neutral position when the mod wheel is in the neutral position again.

I don't understand this reasoning. How would the clipping occur, why is this a problem and why can it only occur if we allow "later manual movements" and "movements by other mappings" to happen?

@jackmau
Copy link
Author

jackmau commented Oct 27, 2021

See here. I

Very ambititous, I have never used realearn for this type of mappings, but I get the point of your project when working with hardware synth/controller. My use of realearn is slightly different, I mostly use it as an input/output bridge for performance controls and OSC based ipad control to be recorded and controlling Playtime.

think you could achieve the same result if I make available the "last target value" as e.g. y_last

+1, this would be very neat

Additionally, add a dedicated "Performance control" mode that considers the last target value as "Target Min" (if "Reverse" unticked) or "Target Max" (if "Reverse" ticked).

  • This mode is a convenience feature and easier to use if you don't need the amount of control that a EEL control transformation gives you.
  • This way of offering a dedicated convenience mode has tradition in ReaLearn. The "Toggle buttons" mode is also just a convenience. You could simulate it in "Normal" mode as well using the right EEL control transformation.

I used to do that before the toggle button was introduced, in the old Realearn 1.X world. I love the idea of having a separate mode for convenience.

Concerning "last target value": @helgoboss said this might mean "the last target value before this particular mapping was activated"

No, I said that, which as you pointed out would lead to inconsistencies, with changes on the UI or from other mappings (and I would add preset changes as well). @helgoboss, already having noted my mistake defined y_last as

the last value of the target which is not changed via this particular mapping

which I think would include changes made by other mappings, i.e. y_last would update if controlled by a non-Perfomance control, do you confirm that this is what you meant @helgoboss ?

I would probably extend the definition to the last value of the target which is not changed by any perfomance mapping, as you could have two performance mappings (like velocity and modwheel) controlling the same target.

@vonglan
Copy link

vonglan commented Oct 27, 2021

I think you could achieve the same result if I make available the "last target value" as e.g. y_last.

Would y_last be on the same 0 to 1 scale (where 0 = target_min and 0 = target_max)?

My current idea based on the input of both of you: ...

Very good.

I don't understand this reasoning. How would the clipping occur, why is this a problem and why can it only occur if we allow "later manual movements" and "movements by other mappings" to happen?

I am sure you have already thought about this much more than me. Maybe I am too careful or pessimistic here.
Let me make an example:

  • Target parameter is 30%. last_y is 0.3
  • Mod Wheel is moved from 0 to 0.5 --> target parameter goes to 30% + (100%-30%)*0.5 = 65%
  • In the UI, the user changes the Target Parameter from 65% to 40%
  • does ReaLearn adapt last_y at this moment? To 0.3 - 0.25 = 0.05? What if the target parameter had been reduced to 10% in the UI? Maybe just clip at last_y = 0.0
  • Now the user wiggles the Mod Wheel a tiny bit, in the end remaining at 0.5. If last_y has been adapted to 0.05 by ReaLearn, as a reaction to the UI manipulation, then ReaLearn would now set the target to 5% + (100%-5%)*0.5 = 52.5%, which is a jump up from the 40%. Maybe this jump just has to be accepted.

What if there are two mappings that use last_y to modify the target value? Do they have separate last_y values, because mapping A does not take into account its own effect, and mapping B does not take into account its (different) own effect? Could this lead to feedback loops?

@helgoboss
Copy link
Owner

which I think would include changes made by other mappings, i.e. y_last would update if controlled by a non-Perfomance control, do you confirm that this is what you meant @helgoboss ?

Yes.

I think you could achieve the same result if I make available the "last target value" as e.g. y_last.

Would y_last be on the same 0 to 1 scale (where 0 = target_min and 0 = target_max)?

It would be a value between 0 and 1 where 0 represents the minimum possible target value and 1 the maximum possible target value (just like y already now).

I am sure you have already thought about this much more than me. Maybe I am too careful or pessimistic here. Let me make an example:

  • Target parameter is 30%. last_y is 0.3
  • Mod Wheel is moved from 0 to 0.5 --> target parameter goes to 30% + (100%-30%)*0.5 = 65%

Yes.

  • In the UI, the user changes the Target Parameter from 65% to 40%
  • does ReaLearn adapt last_y at this moment? To 0.3 - 0.25 = 0.05? What if the target parameter had been reduced to 10% in the UI? Maybe just clip at last_y = 0.0

Well, two things:

  • Don't worry about the variable update. All ReaLearn-provided variables in EEL formulas get a fresh value each time the formula executes, so there's no need for ReaLearn to update them proactively. That's a technical detail, shouldn't concern users.
  • However, last_y wouldn't have the value 0.05, it would have the value 0.4! I'm not planning to adjust y_last itself in a relative way (as your thinking suggests).
  • Hence, there can't be any clipping.
  • Now the user wiggles the Mod Wheel a tiny bit, in the end remaining at 0.5. If last_y has been adapted to 0.05 by ReaLearn, as a reaction to the UI manipulation, then ReaLearn would now set the target to 5% + (100%-5%)*0.5 = 52.5%, which is a jump up from the 40%. Maybe this jump just has to be accepted.

Jumps are to be expected when changing y_last (via the UI or non-performance controls)! After all - as @jackmau says - this issue is about absolute control (with scaling), not relative. Takeover modes will apply if necessary.

What if there are two mappings that use last_y to modify the target value? Do they have separate last_y values, because mapping A does not take into account its own effect, and mapping B does not take into account its (different) own effect? Could this lead to feedback loops?

The last_y value for mappings with the same targets will always be the same. No feedback loops because adjustments of last_y (as response to a target value change) will not result in any other target value changes!

@vonglan
Copy link

vonglan commented Oct 29, 2021

However, last_y wouldn't have the value 0.05, it would have the value 0.4! I'm not planning to adjust y_last itself in a relative way (as your thinking suggests).

Ok. That makes it much easier. And tweaking the UI while at the same time using a Performance Controller might be a rare use case anyway.

@jackmau
Copy link
Author

jackmau commented Oct 29, 2021

  • However, last_y wouldn't have the value 0.05, it would have the value 0.4! I'm not planning to adjust y_last itself in a relative way (as your thinking suggests).

+1, I agree with that, and that was my understanding as well

@helgoboss
Copy link
Owner

Next pre-release (pre.15 or full release already, let's see) will contain a partial and experimental implementation of this feature:

  • y_last will be available in control transformation EEL formulas.
  • y_last will (hopefully) always contain the last value of the target that was not changed via this particular mapping.
    • This is the experimental bit of this implementation. It's not always trivial to detect what mapping caused a change. In particular, this doesn't work so reliably now for targets that use polling. So if you experience issues and the target allows you to switch polling off (such as the "FX: Set parameter value" target), try switching it off ... the only negative side effect of switching polling off is that feedback might not be updated correctly in particular cases.
    • Defining y_last as the last value of the target which is not changed by any perfomance mapping (as @jackmau requested) would be even more tricky. Let's skip that for now. I think y_last would be already very useful without that extended definition. Maybe I can add it in future.
  • So you can use a formula like this one to implement performance control: y = y_last + x * (1 - y_last)
  • The convenience mode "Performance control" is not yet available.

helgoboss added a commit that referenced this issue Nov 18, 2021
helgoboss added a commit that referenced this issue Nov 18, 2021
@helgoboss
Copy link
Owner

Okay, that polling issue should be gone.

@helgoboss
Copy link
Owner

y_last is now available in pre.15!

@vonglan @jackmau Would be cool if you could test and give feedback. I tested using the formula y = y_last + x * (1 - y_last).

@vonglan
Copy link

vonglan commented Nov 23, 2021

Great stuff!
Works fine with pitch bend as source, and
y = y_last + (x-0.5)*2 * (x > 0.5 ? (1-y_last) : y_last)
to adjust the target parameter from Min to Max, with the center being at the previous parameter value.

There is still a bug, if I try to do this in Controller mapping, and the target is virtual.
In this case, the upper range of pitch bend works correctly. At the center, the target parameter jumps to 0. In the lower range, the target stays at 0.

{
    kind = "Mapping",
    value = {
        id = "pkBjN6ZsazSyYkVvZnhUY",
        name = "1",
        source = {
            kind = "MidiPitchBendChangeValue",
            channel = 0,
        },
        glue = {
            step_size_interval = {0.01, 0.05},
            step_factor_interval = {1, 5},
            control_transformation = "y = y_last + (x-0.5)*2 * (x > 0.5 ?  (1-y_last) : y_last)",
        },
        target = {
            kind = "Virtual",
            id = "SY/Cutoff",
        },
    },
}

{
    kind = "Mapping",
    value = {
        id = "994e82be-7e12-49e0-a7ce-0a2bb064aa0b",
        name = "18",
        source = {
            kind = "Virtual",
            id = "SY/Cutoff",
        },
        glue = {
            step_size_interval = {0.01, 0.05},
            step_factor_interval = {1, 5},
        },
        target = {
            kind = "FxParameterValue",
            parameter = {
                address = "ById",
                fx = {
                    address = "ByName",
                    chain = {
                        address = "Track",
                    },
                    name = "*Repro-5*",
                },
                index = 60,
            },
        },
    },
}

@vonglan
Copy link

vonglan commented Nov 23, 2021

I just noticed that the pitch bend (touch strip) on the device I was using has a sort of "dead range" when moving upwards from the center position. It stops sending values until I am 30% above center, and then makes up for the lost values by sending them in very rapid succession.
So, the jumping described is due to my controller device, I think.
When I replace Pitch Bend with Modwheel (touch strip), the problem with virtual targets shows as follows:
Source values up to 0.5 result in target value 0. Upwards from there, it moves up to 1.
I think this is the same as saying, that y_last is always 0 for virtual targets.

@helgoboss
Copy link
Owner

Right, it's not usable with virtual targets, forgot to mention. Real targets only. Just put it into the main mapping instead.

I should put this in the docs.

@vonglan
Copy link

vonglan commented Nov 24, 2021

So, it would be a lot of implementation effort to achieve it for virtual targets?
My use case would be with the SY presets:
I could have one Controller preset, with some standard Performance Controller mappings to virtual targets like cutoff, FM amount etc. And then use this with any of the Virtual Analog synths.

@helgoboss
Copy link
Owner

I'm not sure how to solve this elegantly on a conceptual level ... without the need to introduce new concepts. The point is - as always - that a virtual target inherently doesn't have a value. With the "Wrap" option, it was relatively easy to solve this: Introduce a rule that if the controller mapping has it enabled, then the main mapping will have it implicitly enabled, too. This is easy to understand because "Wrap" is just an on/off type of value. But I don't see how this can be an easy solution with something as complex as a control transformation formula. I would then somehow have to "merge" the formula of the controller mapping with the formula of the main mapping. Merge how?

Why not define this for the main mappings? Do you fear the repetition? That you have to enter the formula in each main preset? If that's the case, the new API is supposed to handle this sort of annoyance. I don't see any striking advantage of defining this in the controller mapping.

@vonglan
Copy link

vonglan commented Nov 24, 2021

I'm not sure how to solve this elegantly on a conceptual level ...

Ok. When I think about it, one problem comes to my mind: if one virtual value has two targets (e.g.: Bipolar Envelope Amount, mapped to Unipolar Envelope Amount plus Envelope Invert Switch), which target should provide the y_last? Pragmatic solution: The system could just use the first target that is a range value.

For me, in this case it is not about avoiding repitition, but rather about not breaking the concept of the SY preset combinations.

About the new API: I find it very interesting conceptionally. Is there a discussion somewhere about the different use cases that it enables? I can think of

  • Create a big set of mappings with a much smaller bit of Lua code (avoid copy&paste or manually creating mappings)
  • Easily create a variant of a preset (e.g. introduce "make-relative" to a controller preset)
  • Is it also possible to adapt the currently loaded mappings in a ReaLearn Instance from a Reaper action? Then you could have an action that switches between "absolute" and "make-relative" mode, for example.

@helgoboss
Copy link
Owner

helgoboss commented Nov 24, 2021

Ok. When I think about it, one problem comes to my mind: if one virtual value has two targets (e.g.: Bipolar Envelope Amount, mapped to Unipolar Envelope Amount plus Envelope Invert Switch), which target should provide the y_last? Pragmatic solution: The system could just use the first target that is a range value.

I don't want virtual targets to obtain information from real targets. Partially because it leads to exactly such hacks. The fact that you need to decide for one real target already indicates that this is most likely not the right way to do it. The only thing that would be okay for me is applying a kind of inheritance, such as with the "Wrap" flag. If the "Wrap" flag in the controller mapping is on, it will implicitly be on in the main mapping and will become effective in the main mapping. I'm open to suggestions how to do a similar thing with the transformation formula. But realistically speaking, I don't think I'll implement this any time soon, there are more pressing things, especially considering that there's a simple alternative.

About the new API: I find it very interesting conceptionally. Is there a discussion somewhere about the different use cases that it enables? I can think of

  • Create a big set of mappings with a much smaller bit of Lua code (avoid copy&paste or manually creating mappings)

That's the main use case.

  • Easily create a variant of a preset (e.g. introduce "make-relative" to a controller preset)

Also, yes.

  • Is it also possible to adapt the currently loaded mappings in a ReaLearn Instance from a Reaper action? Then you could have an action that switches between "absolute" and "make-relative" mode, for example.

With JSON, this has been possible for some time already, using ReaScript (search for set-state in the user guide). But first, it's only applicable for setting the complete ReaLearn instance state and second, It's not yet possible with the new Lua API. As soon as I consider the Lua API stable enough to commit to it, I will make this possible.

However, for the use case of switching between "absolute" and "make-relative", I would suggest to not replace all mappings. That would be overkill. You could instead use Lua to generate each mapping twice, one with "absolute" and one with "make-relative". They would all be loaded in ReaLearn at once. Then you can use one of ReaLearn's many possibilities to enable/disable the correct mappings (conditional activation, tags, etc.). I suppose you could do that already now.

@vonglan
Copy link

vonglan commented Nov 26, 2021

However, for the use case of switching between "absolute" and "make-relative", I would suggest to not replace all mappings. That would be overkill. You could instead use Lua to generate each mapping twice, one with "absolute" and one with "make-relative". They would all be loaded in ReaLearn at once. Then you can use one of ReaLearn's many possibilities to enable/disable the correct mappings (conditional activation, tags, etc.). I suppose you could do that already now.

Agree.

@helgoboss
Copy link
Owner

Added convenient "Performance control" mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request high priority
Projects
None yet
Development

No branches or pull requests

3 participants