beast
beast copied to clipboard
Feature Request: support portamento and logscale parameters
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).
On 12.05.2017 21:03, swesterfeld wrote:
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
Here's what Octave gives me for a polynomial approximation of third degree (degree 2 doesn't fit well) to those points:
x = [0, 1, 2, 3, 4, 5, 6, 7, 8]
y = [0, 1.92, 15.7, 53.9, 124, 244, 422, 672, 1000]
z = 0:0.001:8
p = polyfit (x,y,3)
plot (x, y, 'o-r', z, p(1)*z.^3+p(2)*z.^2+p(3)*z+p(4));
format long
p
p = 1.9500336700336658 0.0536435786436373 -0.1588287638289820
0.1167676767678972
-- Yours sincerely, Tim Janik
https://testbit.eu/timj/ Free software 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.
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
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;
}
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
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.
Stefan, can you please work this into a pull request?
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.
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.