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

Feature Request: support portamento and logscale parameters #5

Open
swesterfeld opened this issue May 12, 2017 · 9 comments
Open

Feature Request: support portamento and logscale parameters #5

swesterfeld opened this issue May 12, 2017 · 9 comments
Labels
📃 Code PR, patch or code needs merging 🍦 New-Feature

Comments

@swesterfeld
Copy link
Collaborator

It might be nice to support portamento via mono synths. Previously this was this bugzilla bug:

https://bugzilla.gnome.org/show_bug.cgi?id=353137

I've improved the portamento implementation, speed can now be set via time (ms). The module should work fine, however I don't like the linear time property, as small times can't be edited well. Here is a suggestion of how the mapping slider -> time could work:

0 0
1 1.92
2 15.7
3 53.9
4 124
5 244
6 422
7 672
8 1000

I attached the implementation as zip as github doesn't allow me to attach them individually (unsupported file type).

bseportamento.zip

@tim-janik
Copy link
Owner

tim-janik commented May 12, 2017 via email

@swesterfeld
Copy link
Collaborator Author

Ok, thats nice, but I don't like the approach of hardcoding some magic (from octave) coefficients in the code, because you can't tweak them to the plugins needs. We have estimated four parameters here, so for each such parameter set, we need four input points to get a function that goes through these parameters.

So what I'd like to have (API wise) would be something like this:

  • min
  • max
  • center
  • slope

where min max and center directly corresponds to the points for these positions. Slope is the parameter which determines some curviness, and can be set indiviudally to choose one particular function from the set of possible ones.

Ideally this would be well-behaved in the sense that whatever the input is, the resulting function is monotonically increasing. So it might be better not to use polynomials (which don't have this property), but fit some exponential function to the specs (which is always monotonically increasing).

Note that for the portamento plugin, we don't need a perfect fit to the data, but just something which goes through min|max|center, is monotonically increasing, and looks similar to the data.

@swesterfeld
Copy link
Collaborator Author

Just as a note, your approximation is not monotonically increasing: using gnuplot:
f(x)=(1.9500336700336658 * x * x * x + 0.0536435786436373 * x * x + -0.1588287638289820 * x + 0.1167676767678972)
plot [0:8] "time-ms-data" using 1:(log($2)), log(f(x))

which goes down and then up between 0 and 1

@swesterfeld
Copy link
Collaborator Author

I think I found a function family that does the job. In gnuplot:

xparam(x,slope) = (exp (x * slope) - x * slope - 1) / (exp (slope) - slope - 1)
f(x)=(1.9500336700336658 * x * x * x + 0.0536435786436373 * x * x + -0.1588287638289820 * x + 0.1167676767678972)
plot [0:1] xparam(x,3)*1000, f(x*8)

The slope parameter (here slope=3) can be adjusted. Note that none of the xparam functions fit the data exactly. But I think they mimic the general behaviour quite well, so a slider using these functions should be user friendly.

Higher slope means more editing space for low values. All xparam functions are monotonically increasing, and therefore also invertible. However I found no direct solution to compute the inverse function, so I implemented a bisection search. Here is my code as C functions:

double
sm_xparam (double x, double slope)
{
  return (exp (x * slope) - x * slope - 1) / (exp (slope) - slope - 1);
}

double
sm_xparam_inv (double x, double slope)
{
  double xlow = 0.0;
  double xhigh = 1.0;

  for (int i = 0; i < 20; i++)
    {
      const double xnew = (xlow + xhigh) / 2;
      const double vnew = sm_xparam (xnew, slope);
      if (vnew < x)
        {
          xlow = xnew;
        }
      else
        {
          xhigh = xnew;
        }
    }
  return (xlow + xhigh) / 2;
}

@tim-janik
Copy link
Owner

tim-janik commented May 18, 2017

I think the slope approach is a bit over the top...
You're right about the monotonically increasing requirement though.
Looking at the function again, a much better approximation in practice is probably:
f(x) = 1.953125 * x^3
The first factor is simply an artefact of your boundaries, 1000./(888)=1.953125000
UPDATE: Btw, if you really wanted to adjust the slope, within [0:1] you could simply use:
f(x) = x^slope | where slope >=1

@swesterfeld
Copy link
Collaborator Author

Yes, thats what I was looking for. Using f(x)=x^slope is not only always monotonically increasing and adjustable, it is also trivial to invert (x^(1.0/slope)).

I can think of only one reason to use x^3 rather than the generic x^slope: if we had all properties automatable and some value ramp for portamento glide, then we would possibly want to compute the mapping from input modulation interval [0,1] to portamento glide at every sample (for this particular module it doesn't matter, but there may be time parameters that can be tweaked at runtime like compressor-attack-ms or adsr-attack-ms or delay-ms). Modulation should in general use a non-linear mapping from control value to dsp parameter, I think the obvious choice would be to use the same mapping like for the ui slider.

Anyway, this is just speculation about the far future. At this point in time, I'd rather have a param spec with slope that is "slightly inefficient", works for the ui, and is flexible, like x^slope.

Question is: who should implement it? I have grepped for log scale through the source, and it seems this affects code in SFI, BSE, BEAST, GXK. And it looks like this is lots of code, too. I had hoped for something simpler ...

I can try to read my way through all this, and come up with an implementation. Or, since you already know the code well, you could probably do it more elegantly and with less effort. Basically let me know if you want me to provide the code for the param spec or do it yourself.

@tim-janik
Copy link
Owner

Stefan, can you please work this into a pull request?

@tim-janik
Copy link
Owner

Yes, thats what I was looking for. Using f(x)=x^slope is not only always monotonically increasing and adjustable, it is also trivial to invert (x^(1.0/slope)).

Indeed, hadn't thought of it in terms of inversion, but I needed that for implementing logscale knobs for the new Processor parameters (cannow be found in ebeast/b/pro-input.vue), It turns out slope is determined by specifying a logscale center like so:

// Determine exponent, so that:
//   begin + pow (0.0, exponent) * (end - begin) == begin  ← exponent is irrelevant here
//   begin + pow (0.5, exponent) * (end - begin) == center
//   begin + pow (1.0, exponent) * (end - begin) == end    ← exponent is irrelevant here
// I.e. desired: log_0.5 ((center - begin) / (end - begin))

Example in gnuplot:

begin=32.7; end=8372; center=523; e=log((end-begin) / (center-begin)) / log(2)
print e; set logscale y; plot [0:1] begin + x**e * (end-begin), center

I can think of only one reason to use x^3 rather than the generic x^slope: if we had all properties automatable and some value ramp for portamento glide, then we would possibly want to compute the mapping from input modulation interval [0,1] to portamento glide at every sample (for this particular module it doesn't matter, but there may be time parameters that can be tweaked at runtime like compressor-attack-ms or adsr-attack-ms or delay-ms). Modulation should in general use a non-linear mapping from control value to dsp parameter, I think the obvious choice would be to use the same mapping like for the ui slider.

I have just added logscale mappings to the Attack/Decay Env in Blepsynth in milliseconds: min=0, max=8000, logcenter=1000
With the above formulas, that results in e=3: 0 + pow (x, 3) * 8, i.e. slope=3; f(x)=8*x^slope;
But for the cutoff frequency, a completely different center was needed, so the slope there is indeed different, more like 4.75 - 5.

@tim-janik tim-janik changed the title Feature Request: support portamento Feature Request: support portamento and logscale parameters Jul 11, 2020
@swesterfeld
Copy link
Collaborator Author

I have just added logscale mappings

The mappings you added are obviously useful, especially for milliseconds (x^3) mappings, but these are not logscale. A true logscale mapping plots as line if you set logscale y in gnuplot. A true logscale mapping is fully determined by two points, a center is not necessary. Here is how that would compare to the function you implemented:

begin=32.7; end=8372; center=523; e=log((end-begin) / (center-begin)) / log(2)
print e; set logscale y; plot [0:1] begin + x**e * (end-begin), center, exp (log (begin) + x * (log (end) - log (begin)))

You can also see that this is not true logscale in BlepSynth frequency knob. It has more pixels for 20..40 Hz than it has for 12000..24000 Hz, even though what we wanted is that each octave takes the same number of pixels.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📃 Code PR, patch or code needs merging 🍦 New-Feature
Projects
None yet
Development

No branches or pull requests

2 participants