|
|
|
03-26-2022, 08:25 AM
|
#1
|
Human being with feelings
Join Date: Aug 2009
Location: NL
Posts: 1,453
|
JSFX: Switch statement.
I noticed that a fair amount of time in some of my more complicated plugins that have a lot of selectable options is being spent in linear chains of if-statements.
Since we don't really have dynamic function calls (like function pointers or something similar), I kind of rely on this pattern a lot.
I noticed that converting the following linear chain
Code:
(foo == 1) ? (
) : (foo == 2) ? (
) : (foo == 3) ? (
) : (foo == N) ? (
);
into a hierarchical one conferred a large performance boost:
Code:
(foo < 25) ? (
(foo == 1) ? (
) : (foo == 2) ? (
) : (foo == 24) ? (
)
) : (
(foo == 25) ? (
) : (foo == 25) ? (
) : (foo == 26) ? (
) : (foo == N) ? (
)
);
This made me wonder whether it'd be possible to have something like a switch statement in JSFX that just acts like a jump table to the active code.
I do realize that I'm probably using JSFX a bit beyond the scope of what it was really intended for though, so it's cool if this is out of scope. Just thought that if it was easy to do it'd be cool. If not, the hierarchical if-statements work well enough
|
|
|
03-27-2022, 03:40 PM
|
#2
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
|
Yeah ! C++ would be really nice !
ROFL
|
|
|
03-28-2022, 12:39 PM
|
#3
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
Hmm, it would probably be possible to add to EEL2 a keyword with usage of something like:
Code:
switch(x,
0: code,
1: code,
2: code,
[ default_code]);
the caveat would be that the 0/1/2 values would need to be constant values. Probably would be most useful if x was rounded to the nearest integer, too?
|
|
|
03-28-2022, 01:47 PM
|
#4
|
Human being with feelings
Join Date: Mar 2021
Posts: 463
|
I like the idea !
Code:
switch(x,
0: (
//Some
//Multi
//Line
//Code
),
1: (
//Some
//Multi
//Line
//Code
),
(
//Some
//Multi
//Line
//Code
)
);
vs
Code:
x == 0 ? (
//Some
//Multi
//Line
//Code
) : x == 1 ? (
//Some
//Multi
//Line
//Code
) : (
//Some
//Multi
//Line
//Code
);
The switch is a bit more readable to me.
Would that come with the performance improvement described by sai'ke ?
|
|
|
03-28-2022, 03:19 PM
|
#5
|
Human being with feelings
Join Date: Aug 2009
Location: NL
Posts: 1,453
|
Quote:
Originally Posted by Justin
Hmm, it would probably be possible to add to EEL2 a keyword with usage of something like:
Code:
switch(x,
0: code,
1: code,
2: code,
[ default_code]);
the caveat would be that the 0/1/2 values would need to be constant values. Probably would be most useful if x was rounded to the nearest integer, too?
|
Sounds great to me! Would the code blocks in that case be allowed to be multi-line if enclosed by parentheses (the way souk21 suggests), or would they have to be single lines?
And yeah, I was assuming that the cases would have to be fixed, but they would be for the typical algorithm selection use case.
And yes on the rounding. Though I'm unsure whether nearest or truncation is more desirable. From other languages, I'd usually expect the latter, but I think you should probably pick what you think is most in line with what is typical in JSFX. For the use cases I have in mind it wouldn't make a difference which one you'd pick I think.
Last edited by sai'ke; 03-28-2022 at 03:25 PM.
|
|
|
05-13-2022, 07:25 PM
|
#6
|
Human being with feelings
Join Date: Oct 2019
Posts: 1,075
|
Return and break too
I'm having trouble writing a MIDI JSFX because I'm missing break or return--a way to exit a loop prematurely. Or a continue statement to cut it short if it's substantial in size.
So here's a similar request I just made for these statements: https://forum.cockos.com/showthread.php?t=266629
|
|
|
05-14-2022, 01:35 AM
|
#7
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
|
In case you did not already find a solution: a (additional) variable needs to be checked in the loop statement to allow for "break" by setting that variable within the loop.
-Michael
|
|
|
05-14-2022, 04:30 AM
|
#8
|
Human being with feelings
Join Date: Oct 2019
Posts: 1,075
|
Of course, but you either place a check every other instruction, or wait for the current iteration to end (if checking, say, in the head of the loop).
|
|
|
05-14-2022, 05:03 AM
|
#9
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
|
Yep. In certain cases you additionally need to "?" check that variable to jump over the rest of the loop.
A "break" instruction might be slightly faster in certain cases.
-Michael
|
|
|
05-14-2022, 06:33 AM
|
#10
|
Human being with feelings
Join Date: Oct 2019
Posts: 1,075
|
Quote:
Originally Posted by mschnell
Yep. In certain cases you additionally need to "?" check that variable to jump over the rest of the loop.
A "break" instruction might be slightly faster in certain cases.
-Michael
|
Apart from efficiency (that an optimizing compiler could at least partially re-gain), the matter is that of ease of coding - legibility and such. An additional '?' yields one more level of nesting and a whole bunch of hairy "dangling else" dangers. I mean: skipping the rest of the current iteration can't be done without more nesting since we have no GOTO (and rightly so, IMHO).
|
|
|
04-19-2023, 04:42 PM
|
#11
|
Human being with feelings
Join Date: Aug 2021
Posts: 128
|
I want this very badly now!
I am currently trying to create a frequency splitter with low CPU usage in JSFX and noticed that the if statement is very CPU intensive.
of this code,
Code:
desc:hyp_exp_29 Fast Frequency Splitter 0.1.3
slider2:band_slider=0<,,{2,3,4}>Band
slider11:frequency_1_slider=100<20,20000,1:log>Frequency 1 (Hz)
slider12:slope_1_slider=1<,,{12 dB/oct,24 dB/oct,36 dB/oct,48 dB/oct,72 dB/oct,96 dB/oct}>Slope 1
slider21:frequency_2_slider=1000<20,20000,1:log>Frequency 2 (Hz)
slider22:slope_2_slider=1<,,{12 dB/oct,24 dB/oct,36 dB/oct,48 dB/oct,72 dB/oct,96 dB/oct}>Slope 2
slider31:frequency_3_slider=5000<20,20000,1:log>Frequency 3 (Hz)
slider32:slope_3_slider=1<,,{12 dB/oct,24 dB/oct,36 dB/oct,48 dB/oct,72 dB/oct,96 dB/oct}>Slope 3
//import more_math_functions.jsfx-inc
//import more_graphics.jsfx-inc
@init //----------------------------------------------------------------
ext_nodenorm = 1;
function frac(n)(n-floor(n));
//はやいBilinear
function bilinear_set(shape,freq)
instance(
a1,b0,b1
)
(
/*shape === "low" ? (
a1 = -tan($pi*(freq/srate - 0.25));
b0 = -a1*0.5 + 0.5;
b1 = b0;
) :
shape === "high" ? (
a1 = -tan($pi*(freq/srate - 0.25));
b0 = a1*0.5 + 0.5;
b1 = -b0;
) :*/
shape === "all" ? (
a1 = -tan($pi*(freq/srate - 0.25));
b0 = a1;
b1 = -1;
) :
0;
);
//BiquadのDirect form 2をがんばってBilinear用に短くしたの
function bilinear(x)
instance(
a1,b0,b1
w0,w1
)
(
w1 = w0;
w0 = x + a1*w1;
b0*w0 + b1*w1;
);
//はやいBiquad
function biquad_set(shape,freq,q)
local(
local.w,local.alpha,local.a0
)
(
local.w = 2*$pi*(freq)/srate;
local.alpha = sin(local.w)/(2*q);
shape === "low" ? (
this.b0 = (1 - cos(local.w))/2;
this.b1 = 1 - cos(local.w);
this.b2 = (1 - cos(local.w))/2;
local.a0 = 1 + local.alpha;
this.a1 = -2*cos(local.w);
this.a2 = 1 - local.alpha;
) :
shape === "high" ? (
this.b0 = (1 + cos(local.w))/2;
this.b1 = -(1 + cos(local.w));
this.b2 = (1 + cos(local.w))/2;
local.a0 = 1 + local.alpha;
this.a1 = -2*cos(local.w);
this.a2 = 1 - local.alpha;
) :
shape === "all" ? (
this.b0 = 1 - local.alpha;
this.b1 = -2*cos(local.w);
this.b2 = 1 + local.alpha;
local.a0 = 1 + local.alpha;
this.a1 = -2*cos(local.w);
this.a2 = 1 - local.alpha;
) :
0;
//あらかじめノーマライズ (ついでに-もここで入れておく)
this.b0 = this.b0/local.a0;
this.b1 = this.b1/local.a0;
this.b2 = this.b2/local.a0;
this.a1 = -this.a1/local.a0;
this.a2 = -this.a2/local.a0;
);
//transposed direct form 2
function biquad(x)
instance(
a1,a2,b0,b1,b2
s1,s2
y0
)
(
y0 = b0*x + s1;
s1 = s2 + b1*x + a1*y0;
s2 = b2*x + a2*y0;
y0;
);
//好きなLRとかBWとかLPFとかAPFとかスロープを組み合わせて使えるやつ
function filter_set(mode,shape,freq,slope)
(
this.slope = slope;
//Linkwitz-Riley
match("LR*",mode) ? (
//LR 12 dB/oct
this.slope === 12 ? (
this.1.biquad_set(shape,freq,0.5);
) :
//LR 24 dB/oct
this.slope === 24 ? (
this.1.biquad_set(shape,freq,sqrt(0.5));
this.2.biquad_set(shape,freq,sqrt(0.5));
) :
//LR 36 dB/oct
this.slope === 36 ? (
this.1.biquad_set(shape,freq,1);
this.2.biquad_set(shape,freq,1);
this.3.biquad_set(shape,freq,0.5);
) :
//LR 48 dB/oct
this.slope === 48 ? (
this.1.biquad_set(shape,freq,0.5/cos($pi/8*1));
this.2.biquad_set(shape,freq,0.5/cos($pi/8*3));
this.3.biquad_set(shape,freq,0.5/cos($pi/8*1));
this.4.biquad_set(shape,freq,0.5/cos($pi/8*3));
) :
//LR 72 dB/oct
this.slope === 72 ? (
this.1.biquad_set(shape,freq,0.5/cos($pi/12*1));
this.2.biquad_set(shape,freq,0.5/cos($pi/12*3));
this.3.biquad_set(shape,freq,0.5/cos($pi/12*5));
this.4.biquad_set(shape,freq,0.5/cos($pi/12*1));
this.5.biquad_set(shape,freq,0.5/cos($pi/12*3));
this.6.biquad_set(shape,freq,0.5/cos($pi/12*5));
) :
//LR 96 dB/oct
this.slope === 96 ? (
this.1.biquad_set(shape,freq,0.5/cos($pi/16*1));
this.2.biquad_set(shape,freq,0.5/cos($pi/16*3));
this.3.biquad_set(shape,freq,0.5/cos($pi/16*5));
this.4.biquad_set(shape,freq,0.5/cos($pi/16*7));
this.5.biquad_set(shape,freq,0.5/cos($pi/16*1));
this.6.biquad_set(shape,freq,0.5/cos($pi/16*3));
this.7.biquad_set(shape,freq,0.5/cos($pi/16*5));
this.8.biquad_set(shape,freq,0.5/cos($pi/16*7));
);
) :
//Butterworth
match("BW*",mode) ? (
//BW 6 dB/oct
this.slope === 6 ? (
this.1.bilinear_set(shape,freq);
) :
//BW 12 dB/oct
this.slope === 12 ? (
this.1.biquad_set(shape,freq,sqrt(0.5));
) :
//BW 18 dB/oct
this.slope === 18 ? (
this.1.biquad_set(shape,freq,1);
this.2.bilinear_set(shape,freq);
) :
//BW 24 dB/oct
this.slope === 24 ? (
this.1.biquad_set(shape,freq,0.5/cos($pi/8*1));
this.2.biquad_set(shape,freq,0.5/cos($pi/8*3));
) :
//BW 36 dB/oct
this.slope === 36 ? (
this.1.biquad_set(shape,freq,0.5/cos($pi/12*1));
this.2.biquad_set(shape,freq,0.5/cos($pi/12*3));
this.3.biquad_set(shape,freq,0.5/cos($pi/12*5));
) :
//BW 48 dB/oct
this.slope === 48 ? (
this.1.biquad_set(shape,freq,0.5/cos($pi/16*1));
this.2.biquad_set(shape,freq,0.5/cos($pi/16*3));
this.3.biquad_set(shape,freq,0.5/cos($pi/16*5));
this.4.biquad_set(shape,freq,0.5/cos($pi/16*7));
);
);
this.polarity = match("*-",mode) ? -1 : 1;
);
function filter(x)
(
x *= this.polarity;
this.slope === 6 ? (
this.1.bilinear(
x
);
) :
this.slope === 12 ? (
this.1.biquad(
x
);
) :
this.slope === 18 ? (
this.2.bilinear(
this.1.biquad(
x
));
) :
this.slope === 24 ? (
this.2.biquad(
this.1.biquad(
x
));
) :
this.slope === 36 ? (
this.3.biquad(
this.2.biquad(
this.1.biquad(
x
)));
) :
this.slope === 48 ? (
this.4.biquad(
this.3.biquad(
this.2.biquad(
this.1.biquad(
x
))));
) :
this.slope === 72 ? (
this.6.biquad(
this.5.biquad(
this.4.biquad(
this.3.biquad(
this.2.biquad(
this.1.biquad(
x
))))));
) :
this.slope === 96 ? (
this.8.biquad(
this.7.biquad(
this.6.biquad(
this.5.biquad(
this.4.biquad(
this.3.biquad(
this.2.biquad(
this.1.biquad(
x
))))))));
);
);
function LR_MP_set(band,freq1,slope1,freq2,slope2,freq3,slope3)
(
this.band = band;
this.band === 2 ? (
this.1.filter_set("LR","low",freq1,slope1);
this.2.filter_set(frac(slope1/24) ? "BW-" : "BW","all",freq1,slope1/2);
) :
this.band === 3 ? (
this.1.filter_set("LR","low",freq2,slope2);
this.2.filter_set("LR","low",freq1,slope1);
this.3.filter_set(frac(slope1/24) ? "LR-" : "LR","high",freq1,slope1);
this.4.filter_set(frac(slope2/24) ? "BW-" : "BW","all",freq2,slope2/2);
this.5.filter_set(frac(slope1/24) ? "BW-" : "BW","all",freq1,slope1/2);
) :
this.band === 4 ? (
this.1.filter_set("LR","low",freq3,slope3);
this.2.filter_set("LR","low",freq2,slope2);
this.3.filter_set("LR","low",freq1,slope1);
this.4.filter_set(frac(slope1/24) ? "LR-" : "LR","high",freq1,slope1);
this.5.filter_set(frac(slope2/24) ? "LR-" : "LR","high",freq2,slope2);
this.6.filter_set(frac(slope1/24) ? "BW-" : "BW","all",freq1,slope1/2);
this.7.filter_set(frac(slope3/24) ? "BW-" : "BW","all",freq3,slope3/2);
this.8.filter_set(frac(slope2/24) ? "BW-" : "BW","all",freq2,slope2/2);
this.9.filter_set(frac(slope1/24) ? "BW-" : "BW","all",freq1,slope1/2);
) :
0;
);
function LR_MP(x)
local(
a,b
)
(
this.band === 2 ? (
this.1 = this.1.filter(x);
this.2 = this.2.filter(x) - this.1;
) :
this.band === 3 ? (
a = this.1.filter(x);
this.1 = this.2.filter(a);
this.2 = this.3.filter(a);
b = this.4.filter(x);
this.3 = this.5.filter(b - a);
) :
this.band === 4 ? (
a = this.1.filter(x);
b = this.2.filter(a);
this.1 = this.3.filter(b);
this.2 = this.4.filter(b);
this.3 =
this.6.filter(
this.5.filter(
a
));
this.4 =
this.9.filter(
this.8.filter(
this.7.filter(x) - a
));
);
);
function slider_to_slope(x)
(
x === 0 ? 12 :
x === 1 ? 24 :
x === 2 ? 36 :
x === 3 ? 48 :
x === 4 ? 72 :
x === 5 ? 96 :
0;
);
@slider //----------------------------------------------------------------
//slider_show
slider_show(frequency_2_slider,band_slider>0);
slider_show(slope_2_slider,band_slider>0);
slider_show(frequency_3_slider,band_slider>1);
slider_show(slope_3_slider,band_slider>1);
spl0.LR_MP_set(
band_slider + 2,
frequency_1_slider,slider_to_slope(slope_1_slider),
frequency_2_slider,slider_to_slope(slope_2_slider),
frequency_3_slider,slider_to_slope(slope_3_slider)
);
spl1.LR_MP_set(
band_slider + 2,
frequency_1_slider,slider_to_slope(slope_1_slider),
frequency_2_slider,slider_to_slope(slope_2_slider),
frequency_3_slider,slider_to_slope(slope_3_slider)
);
//clear band (最後にx.1~x.4をまとめて出力するようにしてるから 使わないbandはここで0を入れておく)
spl0.1 = 0;
spl1.1 = 0;
spl0.2 = 0;
spl1.2 = 0;
spl0.3 = 0;
spl1.3 = 0;
spl0.4 = 0;
spl1.4 = 0;
@sample //----------------------------------------------------------------
spl0.LR_MP(spl0);
spl1.LR_MP(spl1);
spl0 = spl0.1;
spl1 = spl1.1;
spl2 = spl0.2;
spl3 = spl1.2;
spl4 = spl0.3;
spl5 = spl1.3;
spl6 = spl0.4;
spl7 = spl1.4;
The if statement in function filter(x) is very CPU intensive.
We measured CPU usage for various writing styles in the 4 Band split.
The fixed SLOPE without the if statement to begin with was still the fastest, and the current if statement had high CPU usage overall.
The way Saike showed me how to write the if statement has low CPU usage overall, but with the low Slope setting I exceeded the CPU usage of the current if statement.
And I thought a feature request for a switch statement would solve this problem.
I'm wondering if it's possible to do something like this with the switch x?
Code:
x = 0.1;
switch(x,
0.0: code,
0.1: code,
0.2: code,
default_code
);
Code:
x = "A";
switch(x,
"A": code,
"B": code,
"C": code,
default_code
);
But if it is faster to use only integers like [-2,-1,0,1,2], then I prefer only integers!
And if it is even faster by not including negative numbers like [0,1,2,3], then I want it!
As for integer rounding, I prefer the type of rounding that I often see in JSFX, such as loop(0,i+=1) = loop(0.999999999999,i+=1) or 0[] = 0.9999[], because I feel that the behavior is consistent within JSFX and the calculation speed seems fast. It is.
Also, is it possible to do something like this?
Code:
y = switch(x,0: code,1: code,2: code,default_code);
Code:
function f(x)
(
switch(x,
0: code,
1: code,
2: code,
default_code
);
);
y = f(x);
Or is it likely to be faster in this way?
Code:
switch(x,
0: y = code,
1: y = code,
2: y = code,
y = default_code
);
Code:
function f(x)
local(y)
(
switch(x,
0: y = code,
1: y = code,
2: y = code,
y = default_code
);
y;
);
y = f(x);
For my part, I'm happy with the faster way, even if the code is a bit more complicated.
Omit default_code.
Code:
switch(x,
0: code,
1: code,
2: code
);
I would also like to use this way of writing.
Code:
switch(x,
0: code,
1: code,
2: code,
0
);
But if writing it this way gives the fastest processing, I would like to do so.
Now this is the code I most actually want to use
Code:
function filter(x)
(
x *= this.polarity;
switch(this.slope,
6: this.1.bilinear(x),
12: this.1.biquad(x),
18: this.2.bilinear(this.1.biquad(x)),
24: this.2.biquad(this.1.biquad(x)),
36: this.3.biquad(this.2.biquad(this.1.biquad(x))),
48: this.4.biquad(this.3.biquad(this.2.biquad(this.1.biquad(x)))),
72: this.6.biquad(this.5.biquad(this.4.biquad(this.3.biquad(this.2.biquad(this.1.biquad(x)))))),
96: this.8.biquad(this.7.biquad(this.6.biquad(this.5.biquad(this.4.biquad(this.3.biquad(this.2.biquad(this.1.biquad(x))))))))
);
);
spl0 = a.filter(spl0);
Code:
function filter(x)
local(y)
(
x *= this.polarity;
switch(this.slope,
6: y = this.1.bilinear(x),
12: y = this.1.biquad(x),
18: y = this.2.bilinear(this.1.biquad(x)),
24: y = this.2.biquad(this.1.biquad(x)),
36: y = this.3.biquad(this.2.biquad(this.1.biquad(x))),
48: y = this.4.biquad(this.3.biquad(this.2.biquad(this.1.biquad(x)))),
72: y = this.6.biquad(this.5.biquad(this.4.biquad(this.3.biquad(this.2.biquad(this.1.biquad(x)))))),
96: y = this.8.biquad(this.7.biquad(this.6.biquad(this.5.biquad(this.4.biquad(this.3.biquad(this.2.biquad(this.1.biquad(x))))))))
);
y;
);
spl0 = a.filter(spl0);
Please help us....
Translated with www.DeepL.com/Translator (free version)
|
|
|
04-19-2023, 10:55 PM
|
#12
|
Human being with feelings
Join Date: Jun 2013
Location: Krefeld, Germany
Posts: 14,686
|
Would it not be possible to do nested if statements on slope being greater than and call the first biquad in the outer if and the next only in the inner ones, using the result of the outer ? Seems even more efficient than "switch"
Another option might be do multiple versions of the complete code. This can be doen rather efficiently using the new "print" macro feature, ELL offers.
-Michael
|
|
|
04-22-2023, 05:43 AM
|
#13
|
Human being with feelings
Join Date: Aug 2021
Posts: 128
|
I may have been mistaken about the Switch statement. mschnell, I tried the way you taught me to write the if statement and achieved the fastest processing so far, except for the one with fixed slopes without the if statement! Thank you very much for teaching me!
Translated with www.DeepL.com/Translator (free version)
Last edited by lewloiwc; 04-24-2023 at 07:40 PM.
|
|
|
11-15-2023, 02:57 AM
|
#14
|
Human being with feelings
Join Date: Apr 2009
Posts: 91
|
wondering if there has been any progress on implementing Switch-Case in JSFX, or if anything is anticipated in the near future, but which hasn't yet been updated in this thread ?
I need to implement something like this (pseudo-code):
Code:
Function ProcessByType(Type, otherdata)
(Type = A: CodeA(otherdata)
Type = B: CodeB(otherdata)
Type = C: CodeC(otherdata)
Type = D: CodeD(otherdata)
Type = E: CodeE(otherdata)
Type = F: CodeF(otherdata)
Type = G: CodeG(otherdata)
Type = H: CodeH(otherdata)
);
The "Type" variable will always be an integer, and will always be one of [A..H]. The case values A..H are all constants and each is about equally likely to occur. The switching function ProcessByType() would be called once each time a new Midi note arrives in @Block.
Don't know whether nested Ifs or just sequential Ifs are the best approach for my purposes, and not sure if its worth adding some kind of "break" variable to all but the first conditional test.
Note that CodeA-CodeH will each be about 5-10 lines of mostly gfx instructions, so I am also unsure whether there is any speed advantage to coding these as separate functions or just write them all in-line in the same function (unattractive, but possibly a bit faster)?
FYI, I am by no means an expert coder, but self-taught, I have managed to create a functioning, stable 700+ line jsfx which does what I need, to which the above would add much-desired functionality.
Thanks for any suggestions.
Last edited by inframan; 11-15-2023 at 03:06 AM.
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 05:32 AM.
|