-
Notifications
You must be signed in to change notification settings - Fork 326
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
ExpValueConverter not working well #574
Comments
Hi, David. This has been a known issue for some time now; if I remember correctly, the log mapping doesn't work either. And linear sliders with unit-increment still seem to output fractional values. I've recently written this set of nonlinear mapping functions with adjustable It would be great if we could put things together and perhaps have more nonlinear functions available. Ideally, we could have four keywords, each for the four curve types, where a specified tension parameter (T) can be set to determine increasing (positive T) slopes or decreasing ones (negative T). Dario |
Thanks for this info and your repo. I'll leave the issue open until it's fixed. The online IDE works fine. It was hard to trace down where the bug is in the C++ side. |
Since std::exp(10000) is "Inf", even in quad precision, "explodes" sounds
correct. - Julius
…On Fri, Apr 16, 2021 at 5:01 PM David Braun ***@***.***> wrote:
Thanks for this info and your repo. I'll leave the issue open until it's
fixed. The online IDE works fine. It was hard to trace down where the bug
is in the C++ side.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#574 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAQZKFNXVN4YKMC6RHN7DVTTJDFXFANCNFSM43CH6LQA>
.
--
Julius O. Smith III ***@***.***>
Professor of Music and, by courtesy, Electrical Engineering
CCRMA, Stanford University
http://ccrma.stanford.edu/~jos/
|
On which architecture do you see that? |
From @orlarey (internal exchange in french translated): It seems to me that the problem is not there. Indeed exp(10000) explodes, but that's normal, it's just math! To control pitches, you should use a logarithmic scale (like the keys of a piano) and not an exponential one ! The following code in FaustLive works very well and has the expected behavior : the middle of the slider corresponds to 220 Hz. I guess it should be the same for Juce ?
But if you run this example in the IDE, it doesn't work. The IDE doesn't do the right conversion... |
Hi, @orlarey. The behaviour that you describe for the frequency slider is right. I'm guessing that Faust implements something like the following; perhaps we could even extend it and add a base parameter:
But could you also check these sliders below?
For example, exp1 appears to be broken, while log0 is essentially always 0 except for the last few pixels of the slider run. It could be due to how my brain is wired and it might as well be wrong, but when I think of piloting a parameter through a logarithmic slider, I think of the slider as a linear input to a log-like function. I think the same way for an exponential slider. That is, I expect more resolution towards the upper side of the slider for a log-like behaviour, and vice versa for the exponential one. The behaviour that I describe appears to be inverted in the other sliders of the last code example. Having maybe one or two more sliders with one of the functions here would be great for me as I currently cannot use nonlinear mapping properly for my systems. I'm not sure if others would also benefit from them. Ciao, |
In case new slider types are still on the table, I think nih-plug handles this very nicely: |
@magnetophon I also have a proposal here that creates a new slider primitive whose scale is more configurable upfront. |
@DBraun Looks great! |
Why not have a slider that allows for a tension parameter too? The octave-scale frequency mapping shown in the previous examples would just be a special case of a more general exponential mapping function: https://www.desmos.com/calculator/elbwhqa1po. |
Looks great to me as well!
|
@orlarey and @DBraun had a recent mail exchange on this subject or something near, trying to write the wanted behaviour using "widget modulation" and So basically the point is to see is the desired behaviour can be written with the current Faust and compiler. @DBraun can possible explain the situation better than me. |
These are my goals:
Maybe we should start the discussion with what's ideal for a package manager. Should there be both non-GUI and GUI versions of the same function? For example, look at https://faustlibraries.grame.fr/libs/reverbs/#restereo_freeverb.
This happens to be an example in which the parameters are (0-1), which is friendly for modular synthesis, but keep in mind that's not always the case. I think that for the sake of a package distribution, many functions should also be distributed with normalized "safe-input" alternatives with GUIs. For example, there could also be this stereo reverb function: import("stdfaust.lib");
stereo_freeverb_ui(_fb1, _fb2, _damp, _spread, _wetAmount) = hgroup("Reverb", ef.dryWetMixer(wetAmount, reverb))
with {
fb1 = _fb1 + vslider("[0] FB 1 [style:knob]", .1, 0., 1., .01) : aa.clip(0, 1);
fb2 = _fb2 + vslider("[1] FB 2 [style:knob]", .1, 0., 1., .01) : aa.clip(0, 1);
damp = _damp + vslider("[2] Damp [style:knob]", .1, 0., 1., .01) : aa.clip(0, 1);
spread = _spread + vslider("[3] Width [style:knob]", 1, 0., 1., .01) : aa.clip(0, 1);
wetAmount = _wetAmount + hslider("[4] Mix [style:knob]", 1, 0., 1., .01) : aa.clip(0, 1);
reverb = re.stereo_freeverb(fb1, fb2, damp, spread);
};
process = stereo_freeverb_ui; By default, this function exposes every parameter to modular synthesis, rather than requiring it via widget modulation. This is an example of reusing that code: import("stdfaust.lib");
reverbs= library("downloaded_reverb.lib");
wetModulation = os.osc(.5)*.1;
dampModulation = os.osc(.4)*.2;
process = reverbs.stereo_freeverb_ui(0, 0, dampModulation, 0, wetModulation); Personally, I feel that being aware of the parameter indices and passing zero to most of them is less burdensome than widget modulation. Also, because I think the weakness of this code style is how repetitive the internals of This was his snippet, which is general, not about my reverb example. import("stdfaust.lib");
// the circuit we want to modulate
generator = hslider("gain", 0, 0, 1, 0.01) : hbargraph("g", -0.1, 1.1) : *(os.osc(440));
// the modulation circuit, s: slider and m: modulation signal -1..1
mc(s,m) = s + m*mi : max(lo) : min(hi) with {lo=lowest(s); hi=highest(s); mi=(lo+hi)/2;};
process = os.osc(1) * hslider("modulation",0.5,0,1,0.01) : ["gain":mc -> generator]; Note that import("stdfaust.lib");
// the modulation circuit, s: slider and m: modulation signal -1..1
mc(s,m) = s + m*depth : max(lo) : min(hi) : hbargraph("graph 2", lo, hi) with {lo=lowest(s); hi=highest(s); depth=hi-lo;};
myfilter = fi.lowpass(1, cutoff)
with {
FREQ_MIN = 8;
FREQ_MAX = 20050;
cutoff = hslider("cutoff", FREQ_MIN, FREQ_MIN, FREQ_MAX, .01) : hbargraph("graph 1", FREQ_MIN, FREQ_MAX);
};
modulation = button("gate") : si.smoo : _* hslider("modulation",0.5,0,1,0.01);
process = modulation, _ : ["cutoff":mc -> myfilter] <: _, _; We have a frequency cutoff parameter whose range is This approach is ok, but it doesn't actually create a non-linear scale. If you were to use the GUI, you'd get a linear response between 8 and 20050. If you were to use the So let's trying coding it again with the goal of having a custom scale. import("stdfaust.lib");
FREQ_MIN = 8;
FREQ_MAX = 20050;
// scale takes something in [0,1] and remaps to whatever you want.
// Here we have scale(0)==FREQ_MIN and scale(1)==FREQ_MAX
scale(x) = it.interpolate_linear(x, log(FREQ_MIN), log(FREQ_MAX)) : exp;
// the modulation circuit, s: slider and m: modulation signal -1..1
mc(s,m) = s + m*depth : max(lo) : min(hi) : hbargraph("graph 2", lo, hi) with {lo=lowest(s); hi=highest(s); depth=hi-lo;};
myfilter = fi.lowpass(1, cutoff)
with {
cutoff = hslider("cutoff", 0, 0, 1, .01) : scale : hbargraph("graph 1", FREQ_MIN, FREQ_MAX);
};
modulation = button("gate") : si.smoo : _* hslider("modulation",0.5,0,1,0.01);
process = modulation, _ : ["cutoff":mc -> myfilter] <: _, _; Here Let's go back to the GUI-featured reverb and update it: import("stdfaust.lib");
fx_reverb_ui = hgroup("Reverb", ef.dryWetMixer(wetAmount, reverb))
with {
fb1 = vslider("[0] FB 1 [style:knob]", .1, 0., 1., .01);
fb2 = vslider("[1] FB 2 [style:knob]", .1, 0., 1., .01);
damp = vslider("[2] Damp [style:knob]", .1, 0., 1., .01);
spread = vslider("[3] Width [style:knob]", 1, 0., 1., .01);
wetAmount = hslider("[4] Mix [style:knob]", 1, 0., 1., .01);
reverb = re.stereo_freeverb(fb1, fb2, damp, spread);
};
// the modulation circuit, s: slider and m: modulation signal -1..1
mc(s,m) = s + m*mi : max(lo) : min(hi) with {lo=lowest(s); hi=highest(s); mi=(lo+hi)/2;};
wetModulation = os.osc(.5)*.1;
dampModulation = os.osc(.4)*.2;
process = wetModulation, dampModulation, si.bus(2) : ["Mix":mc, "Damp":mc -> fx_reverb_ui]; Like I said earlier, the strength is that the internals of process = reverbs.stereo_freeverb_ui(0, fb2modulation, dampModulation, 0, wetModulation); |
Refactoring the last import("stdfaust.lib");
fx_reverb_ui = hgroup("Reverb", ef.dryWetMixer(wetAmount, reverb))
with {
fb1 = vslider("[0] FB 1 [style:knob]", .1, 0., 1., .01);
fb2 = vslider("[1] FB 2 [style:knob]", .1, 0., 1., .01);
damp = vslider("[2] Damp [style:knob]", .1, 0., 1., .01);
spread = vslider("[3] Width [style:knob]", 1, 0., 1., .01);
wetAmount = hslider("[4] Mix [style:knob]", 1, 0., 1., .01);
reverb = re.stereo_freeverb(fb1, fb2, damp, spread);
};
// the modulation circuit, s: slider and m: modulation signal -1..1
mc(m,s) = s + m*mi : max(lo) : min(hi) with {lo=lowest(s); hi=highest(s); mi=(lo+hi)/2;};
wetModulation = os.osc(.5)*.1;
dampModulation = os.osc(.4)*.2;
process = ["Mix":mc(wetModulation), "Damp":mc(dampModulation) -> fx_reverb_ui]; Note that
|
This is part of my Faust code:
I used faust2juce on Windows:
sh faust2juce -soundfile -midi -nvoices 8 -standalone -jucemodulesdir C:/tools/JUCE/modules synth.dsp
, built and launched the exe with the debugger.I set a breakpoint here:
faust/architecture/faust/gui/ValueConverter.h
Line 286 in c06e9c6
fmax
holds the value 10000, and I think something is going wrong becausestd::exp(10000) explodes
. I would hope ExpValueConverter would be more robust than that.I have a different idea of what it means to do exponential conversion, so maybe this can help. Take the graph
y=b^x
and consider it for x between 0 and 1. The endpoints are (0, 1), and (1, b), so the range of y is [1, b]. Maybe b would equal the constant e, but it can be anything.So suppose we have MIDI values from 0-127. First convert them to 0-1. This is
x
. Then takeb^x
. Then remap from domain [1, b] to the output (40 Hz, 10000 Hz).If I were to re-code an exponential converter, it would be like this (kept slightly verbose for clarity)
When I use this code and use MIDI hardware knobs, it sounds correct to me. The exponential is actually meaningful, and if I change the base value even larger, I'm able to get a lot of fine control over the "low" part of the output range. However, the GUI doesn't reflect it. If I set the physical knobs to halfway position, the GUI says that the Hz is about 4900, regardless of what base I compiled with. Changing the base however does change the sound I hear at this physical knob position. It "sounds" correct, but the GUI is wrong.
Another thing I noticed with the current code is that if I set a breakpoint on ExpValueConverter's
ui2faust
method and wiggle a knob, first I get an update wherex
is the MIDI value between 0-127. Then I immediately get another call toui2faust
in whichx
is the value that is going to be applied to the DSP code and UI. For example,x
would be 5000 (Hz), and I'd see 5000 Hz on the slider and hear the effect of it. I think that's this second call toui2faust
is weird.The text was updated successfully, but these errors were encountered: