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

Add "Don't squeeze" option to discrete targets #184

Open
helgoboss opened this issue Mar 1, 2021 · 11 comments
Open

Add "Don't squeeze" option to discrete targets #184

helgoboss opened this issue Mar 1, 2021 · 11 comments
Labels

Comments

@helgoboss
Copy link
Owner

helgoboss commented Mar 1, 2021

At the moment, discrete targets with a variable about of discrete values (such as "Selected track" and "FX parameter") are most useful with relative encoders or prev/next buttons. With (absolute) faders and knobs they are only useful for birds-eye view navigation, not for precise control. Because the target range is not constant, precise control is impossible and every change in the number of discrete values (e.g. tracks) will let things fall apart.

We could offer an option to make the number of discrete values irrelevant and set a fixed number of relevant values instead.

Another similar thing: "Seek" target currently always relates to the complete time range in question. Great for many use cases. But if you want the seek increment/decrement for to be the same no matter the size of the time range, you need another solution. Either an action, but that would need #129. Or a new feature, maybe related to the "rounding" step size.

@vonglan
Copy link

vonglan commented Mar 19, 2021

sounds similar to the "zoom factor" request #204

@helgoboss
Copy link
Owner Author

@vonglan It's something else.

@helgoboss
Copy link
Owner Author

This would need a new kind of target control value in addition to Absolute (%) and Relative (+/- n): Absolute (n)

@helgoboss
Copy link
Owner Author

helgoboss commented Apr 23, 2021

This should be a new absolute mode "Discrete" or "No scale". It takes absolute discrete control values and passes them on to the target without scaling/squeezing. When adding #315 to the mix, we would then have a quite symmetrical looking set of control value kinds:

Pseudo code:

enum ControlValue {
    /// Already existing: Absolute value that represents a percentage (e.g. fader position on the scale from lowest to 
    /// highest, knob position on the scale from closed to fully opened, key press on the scale from not pressed to
    /// pressed with full velocity, key release).
    AbsoluteContinuous(UnitValue),
    /// New for #184: Absolute value that is capable of retaining the original discrete value, e.g. the played note 
    /// number, without immediately converting it into a UnitValue and thereby losing that information - which is
    /// important for the new "Discrete" mode.
    AbsoluteDiscrete(DiscreteValue),
    /// New for #315: Relative increment that is very specific in that it already expresses a certain step size.
    RelativeContinuous(UnitIncrement),
    /// Already existing: Relative increment (e.g. encoder movement)
    RelativeDiscrete(DiscreteIncrement),
}

/// Already existing: Represents a percentage in the form of a value of the unit interval (0.0 to 1.0).
struct UnitValue {
    // ...
}

/// Already existing: Represents an increment within the positive or negative unit interval (-1.0 to 1.0).
struct UnitIncrement {
    // ...
}

struct DiscreteValue{
    /// Concrete discrete value.
    actual: u32,
    /// Maximum value: Good to know in order to be able to instantly convert to a UnitValue whenever we
    /// want to go absolute-continuous.
    max: u32
}

/// Already existing: Represents a discrete increment, either positive or negative. Example: +5
struct DiscreteIncrement {
    // ...
}

@helgoboss
Copy link
Owner Author

On second thought:

  • You can go from a discrete value to a continuous value at any time but not back.
  • There shouldn't be a absolute mode "Discrete". The tuning section should just try to uphold any incoming discrete value as long as possible (until it hits a processing step that forces the value to become continuous, e.g. EEL control transformation currently).
  • The target should decide if it wants to be hit with a continuous value or discrete value. See the action target as example which can already invoked in multiple different ways with possibly different outcomes.

On third thought:

  • It's not just about upholding a discrete value, it's also about differences in processing them.
  • Continuous processing does scaling, discrete processing doesn't. That happens in the tuning section.
  • [] In which stages do we need to decide whether to use scaling or not? Identify them and decide if it's more feasible to let the target decide about "discrete vs. continuous" or the tuning section.

@vonglan
Copy link

vonglan commented May 19, 2021

Regarding scaling: the scaling based on (source_min ... source_max) and (target_min ... target_max) is logically only possible for absolute sources. But for relative sources, a simple zoom factor (#204 ) would be possible and useful IMHO.

I must say that I do not yet understand the difference between discrete and continuous sources, because technically, I am sure all sources are discrete, as they are digital. To me it seems it is just a ReaLearn convention that for "discrete" sources the smallest increment is encoded as 0.01 or 1%, and then a discrete target can translate that back to "smallest increment" with regard to the target. So I think the difference between the two is just that the discrete increment says to the target "by the way, my smallest delta is xxx, in case you want to use that information".

Could a normal 7-bit potentiometer also be represented as discrete, with the smallest Increment then being 1/127?

@helgoboss
Copy link
Owner Author

helgoboss commented May 19, 2021

Regarding scaling: the scaling based on (source_min ... source_max) and (target_min ... target_max) is logically only possible for absolute sources. But for relative sources, a simple zoom factor (#204 ) would be possible and useful IMHO.

I know, I know, I get the message ;)

I must say that I do not yet understand the difference between discrete and continuous sources, because technically, I am sure all sources are discrete, as they are digital.

Yes, it's all binary in computers but it still makes sense to distinguish between numbers that are countable/discrete (integers) and numbers that are (or at least are perceived as) continuous (floating point numbers). When I say "discrete control value", I mean integers. MIDI only has integers, it doesn't have floating point numbers. In many cases this is just a protocol limitation, e.g. I guess 14-bit CCs are just the MIDI way of controlling something continuous, whereas floating point numbers would be suited better for this purpose.

But there are cases in which the integers in MIDI make much sense, e.g. keys on a piano! There are no other keys between C4 an C#4 ... that means the set of keys is discrete. Up until now, what ReaLearn does is taking the incoming discrete value, e.g. 63 (of 128 possible values) and immediately converts it to the normalized floating point value 0.49606299... by doing 63 / 127. Because it's easy to process normalized values. It also applies scaling from source min/max to target min/max and thereby squeezing or stretching the value range. That's all great and desired - as long as you have a continuous target, such as volume or pan. But as soon as you have a discrete target - such as "Project: Navigate within tracks" (the set of tracks is discrete) - scaling is usually not desired. If you press key 5 on the MIDI keyboard, you probably want to navigate to track 5 in REAPER ... but this is exactly the scenario which is hard to achieve now, especially because the number of tracks can change.

To me it seems it is just a ReaLearn convention that for "discrete" sources the smallest increment is encoded as 0.01 or 1%, and then a discrete target can translate that back to "smallest increment" with regard to the target. So I think the difference between the two is just that the discrete increment says to the target "by the way, my smallest delta is xxx, in case you want to use that information".

For relative control this doesn't apply. This ticket is about improvement of absolute control via discrete sources only.

Could a normal 7-bit potentiometer also be represented as discrete, with the smallest Increment then being 1/127?

It's the goal of this ticket to improve ReaLearn's processing chain so that the 1 stays a 1 from start (incoming source message) till end (a possibly discrete target). Without any scaling/squeezing/streching) applied on the way (huge difference) and without converting things to floating point numbers (which excludes possible numerical rounding errors happening on the way).

@helgoboss
Copy link
Owner Author

  • In which stages do we need to decide whether to use scaling or not? Identify them and decide if it's more feasible to let the target decide about "discrete vs. continuous" or the tuning section.

These are the current operations. The scaling operations among them should be replaced with non-scaling operations (addition/clamping) in discrete mode:

  1. Apply source interval (if source range restricted)
    • Problem: In controller mappings, when the target is virtual (and the source emits discrete value and the source range is restricted), we can't decide yet whether to use scaling or not because we don't know if the target desires continuous or discrete control. We whould offer a choice in the tuning section.
  2. Apply control transformation
    • It's okay to make this a destructive operation which always converts a discrete value into a continuous value.
    • Therefore, a discrete mode with control transformation filled doesn't make sense. Scaling should be applied as usual.
  3. Apply reverse
    • Okay, not a scaling operation.
  4. Apply target interval
    • Target min/max is only available if the target is known, so we know if the target desires continuous or discrete control.
  5. Apply rounding
    • Ignore if value is discrete.

I guess the problem mentioned in point 1 and the fact that it's not just about "retaining" the discreteness of a value but also about applying completely different operations (addition/clamping instead of multiplication) indicates that this should be a property of the tuning section rather than a property of the target.

In any case, it's important that we make already existing controller presets retain incoming discrete values! Without breaking existing behavior of course.

Therefore I think the way to go is this:

  1. Make both absolute modes "Normal" and the new "Discrete" retain the discreteness of incoming control values as far as possible.
    • "As far as possible" means that it stays discrete until meeting a "destructive" operation (an operation which needs to turn the discrete value into a continuous one).
    • Destructive operations in mode "Normal":
      • Source interval scaling (only if source range restricted)
      • Control transformation (only if filled with a valid formula)
      • Target interval scaling (only if target range restricted)
    • Destructive operations in mode "Discrete":
      • Control transformation (only if filled with a valid formula)
  2. Make absolute mode "Normal" use scaling, but only if necessary.
  3. Make absolute mode "Discrete" use addition/clamping instead of scaling.

Consequences:

  • So the existing absolute mode "Normal" basically means "Use scaling if necessary (but postpone the decision whether to stay discrete or to go continuous as long as possible)".
    • Thanks to this convention, existing controller mappings with virtual targets that have absolute mode "Normal" and a non-restricted source range will retain the discreteness of values out-of-the-box. Great!
  • And "Discrete mode" means "Use addition/clamping unless impossible (thereby trying to retain discreteness at all costs)".

@vonglan
Copy link

vonglan commented May 20, 2021

My two cents:

  • Destructive operations in mode "Discrete":

    • Control transformation (only if filled with a valid formula)

I think it would be simpler for the user, if the transformation field would always be inactive and hidden for "discrete". If anyone wants to use the formula, then they can't use "discrete".

@helgoboss
Copy link
Owner Author

I changed my mind about that in the meantime:

  • I will add support for discrete formulas.
  • I will add discrete value flow also for feedback direction.

Then it should be simple: Discrete means discrete all the way (as long as both source and target are discrete).

@vonglan
Copy link

vonglan commented May 22, 2021

Sounds good.

helgoboss added a commit that referenced this issue May 23, 2021
helgoboss added a commit that referenced this issue May 23, 2021
helgoboss added a commit that referenced this issue May 23, 2021
… an absolute mode

because it doesn't just affect absolute processing but target value interpretation
in general (percentages vs. discrete values)
helgoboss added a commit that referenced this issue May 23, 2021
it made tests return false positives because just unit values were compared
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants