|
|
|
04-06-2012, 08:40 AM
|
#1
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
JSFX function support discussion thread
Hello everybody,
Function support has the following features/restrictions: - Functions can only be declared at the top level of JS code (i.e. not within a () block etc).
- Functions defined in @init can be used in any other section, but functions defined elsewhere can only be used in that section.
- Functions can have 0 or more parameters, and any number of local variables.
- Functions can not call recursively (a function can only call functions defined prior to that function)
- Functions have very low overhead, and are automatically inlined if they are below a certain size.
- The last statement to be executed in a function is effectively the return value (matching the logic used elsewhere in JS).
The most basic declarations and use of functions might be:
Code:
@sample
function getSomeValue() (
2;
);
spl1 = spl1 * getSomeValue();
or:
Code:
@sample
function applyGain(spl) (
spl * 2;
);
spl1 = applyGain(spl1); // same as spl1 = spl1 * 2;
or
Code:
@sample
function applyGain(spl,scale) (
spl * scale;
);
spl1 = applyGain(spl1,2); // same as spl1 = spl1 * 2;
If you wish to define local variables for the function, you can do it by adding one or more local() definitions to the declaration:
Code:
@sample
function applyWave(spl)
local(somecounter, scale)
(
scale < 1.0 ? scale += 1.0/srate;
somecounter += 1.0/srate;
spl * sin(somecounter) * scale;
);
spl1 = applyWave(spl1);
Local variable can be used for temporary storage within the function, but they also persist across calls. If you define a function in @init and use it within two other sections (say, @gfx and @sample), your local variables will (now as of April 14) be in separate spaces -- so if you need them shared, you would want to use instance variables.
OK, post any questions or feedback you have here (especially if you see any regressions on existing FX or other code).
Last edited by Justin; 01-17-2014 at 09:15 AM.
|
|
|
04-06-2012, 10:30 AM
|
#2
|
Human being with feelings
Join Date: Mar 2008
Location: Unwired (probably in the proximity of Amsterdam)
Posts: 4,868
|
Wow... That is awesome.
__________________
˙lɐd 'ʎɐʍ ƃuoɹʍ ǝɥʇ ǝɔıʌǝp ʇɐɥʇ ƃuıploɥ ǝɹ,noʎ
|
|
|
04-06-2012, 01:18 PM
|
#3
|
Human being with feelings
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
|
OK, this is pretty awesome. Thanks!
|
|
|
04-06-2012, 01:33 PM
|
#4
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Cool!!1!
|
|
|
04-06-2012, 02:31 PM
|
#5
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
|
w00t!
|
|
|
04-06-2012, 02:56 PM
|
#6
|
Human being with feelings
Join Date: Dec 2011
Posts: 999
|
Great stuff!
It doesn't appear to be possible to use gfx_ draw stuff within functions....
Code:
@gfx 400 400
function drawSquare(tlx, tly, size)
(
x = tlx; y = tly;
gfx_x = tlx;
gfx_y = tly;
gfx_lineto(tlx+size, tly, 1);
gfx_lineto(tlx+size, tly+size, 1);
gfx_lineto(tlx, tly+size, 1);
gfx_lineto(tlx, tly, 1);
//gfx_rectto(200,200);
1;
);
function setColours(r,g b)
(
gfx_r = r;
gfx_g = g;
gfx_g = b;
a = 5;
1;
);
setColours(100,255,255);
gfx_a = 255;
//gfx_r = 255; gfx_g = 255; gfx_b = 255;
setColours(100,255,255);
drawSquare(10,50,100);
gfx_lineto(100,100,1);
SetColours works and x and y are set in drawSquare, but nothing is drawn, although gfx_x and gfx_y change. gfx_lineto draws a line.
It's probably me, although it seems it should work.
|
|
|
04-06-2012, 06:40 PM
|
#7
|
Human being with feelings
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
|
I've had a poke around with this now, pretty cool.
The only thing that feels a bit tricky are functions that need to set multiple bits of state. Eg. I have a bandpass function that needs to store four variables (for each channel - I'm trying to split out common code that used to be repeated for both l and r). I adapted it to use an array for that and performance dropped a lot. Any ideas on having multiple "out" values for functions?
|
|
|
04-07-2012, 02:45 AM
|
#8
|
Human being with feelings
Join Date: May 2008
Location: In Space
Posts: 240
|
Nice
|
|
|
04-07-2012, 05:01 AM
|
#9
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by captain_caveman
It's probably me, although it seems it should work.
|
Yeah, it's you. The gfx_r, gfx_g, gfx_b and gfx_a variables are all in the 0.0..1.0 range, not 0..255. If you change them accordingly (especially gfx_a), then your drawSquare will work just fine.
|
|
|
04-07-2012, 06:18 AM
|
#10
|
Human being with feelings
Join Date: Dec 2011
Posts: 999
|
Quote:
Originally Posted by Tale
Yeah, it's you. The gfx_r, gfx_g, gfx_b and gfx_a variables are all in the 0.0..1.0 range, not 0..255. If you change them accordingly (especially gfx_a), then your drawSquare will work just fine.
|
Ah... *cough*... of course... (mops brow)... it's been a while.
Thanks Tale!
|
|
|
04-07-2012, 09:00 AM
|
#11
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
|
Quote:
Originally Posted by dub3000
Any ideas on having multiple "out" values for functions?
|
How about something like this?
Code:
function f(arg1, arg2, arg3)
out(arg2, arg3)
(
arg2 = 1; // set output var
arg3 = 2; // ^^
1; // normal return val
);
Dunno if that's technically possible though. Justin?
|
|
|
04-07-2012, 09:51 AM
|
#12
|
Human being with feelings
Join Date: Sep 2009
Location: Middle of nowhere (where the cheese comes from)
Posts: 483
|
Quote:
Originally Posted by IXix
How about something like this?
Code:
function f(arg1, arg2, arg3)
out(arg2, arg3)
(
arg2 = 1; // set output var
arg3 = 2; // ^^
1; // normal return val
);
Dunno if that's technically possible though. Justin?
|
can't you just define normal vars as answer_1, answer_2 etc. and put the return values there? I know it's not quite as spiffy, but it should work
|
|
|
04-07-2012, 02:10 PM
|
#13
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
I think what we'll do is actually add two new functions, push() and pop(), and let you just use a stack for this sort of thing. I think we can implement a stack that is very fast and will be generally quite useful -- you could use it for passing extra parameters in, as well as returning multiple values.
(As a side note, I'd like to optimize array access, but of course you have to be super careful not to change any of the rounding behavior, ugh)
|
|
|
04-07-2012, 02:11 PM
|
#14
|
Human being with feelings
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
|
Quote:
Originally Posted by IXix
How about something like this?
Code:
function f(arg1, arg2, arg3)
out(arg2, arg3)
(
arg2 = 1; // set output var
arg3 = 2; // ^^
1; // normal return val
);
Dunno if that's technically possible though. Justin?
|
This would sort me out, yep (I'd split the bandpass into lopass and hi pass to get under the 3 var limit). Any chance of this or something like it?
|
|
|
04-07-2012, 02:13 PM
|
#15
|
Human being with feelings
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
|
Quote:
Originally Posted by Justin
I think what we'll do is actually add two new functions, push() and pop(), and let you just use a stack for this sort of thing. I think we can implement a stack that is very fast and will be generally quite useful -- you could use it for passing extra parameters in, as well as returning multiple values.
(As a side note, I'd like to optimize array access, but of course you have to be super careful not to change any of the rounding behavior, ugh)
|
This will also work.
On the performance thing - 16 array accesses per sample uses 0.7% total CPU of an i5....
|
|
|
04-07-2012, 02:36 PM
|
#16
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
Quote:
Originally Posted by dub3000
This will also work.
On the performance thing - 16 array accesses per sample uses 0.7% total CPU of an i5....
|
Yeah, the big problem is that we index it with a double, so we have to do some rounding, convert to integer, check the bounds, then make sure that that section of RAM is available, then return it... it all adds up
a stack (push/pop, maybe peekstack and peekstack2 too) would be a lot quicker...
|
|
|
04-07-2012, 11:53 PM
|
#17
|
Human being with feelings
Join Date: Sep 2009
Location: Middle of nowhere (where the cheese comes from)
Posts: 483
|
Quote:
Originally Posted by Justin
(As a side note, I'd like to optimize array access, but of course you have to be super careful not to change any of the rounding behavior, ugh)
|
2 different array types maybe?
|
|
|
04-08-2012, 05:59 PM
|
#18
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
Actually I think I managed to speed up array accesses a bit on x86/x86-64, without changing function at all. I will post a new build soon with that (and stack support, which is mostly done but I need to implement on PPC).
For example, a stereo 16-sample bit-bucket style delay line using an array (buf[16]=buf[15]; buf[15]=buf[14]; ...) went from 1.7% CPU use to 1.2% CPU here, woot.
I also am going to look at adding some basic optimization passes, such as detecting expressions that can be resolved at compile time (such as converting x * 1 / 2 into x * 0.5, etc).
|
|
|
04-08-2012, 06:40 PM
|
#19
|
Human being with feelings
Join Date: Feb 2009
Posts: 60
|
Quote:
Originally Posted by Justin
For example, a stereo 16-sample bit-bucket style delay line using an array (buf[16]=buf[15]; buf[15]=buf[14]; ...) went from 1.7% CPU use to 1.2% CPU here, woot.
|
Nice... off topic, but this made me think some builtins to do array shifting, thinking something like perl shift() and unshift() builtins, maybe even a rotateLeft() and rotateRight(), could be useful in JS.
|
|
|
04-08-2012, 07:01 PM
|
#20
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
Quote:
Originally Posted by tonecarver
Nice... off topic, but this made me think some builtins to do array shifting, thinking something like perl shift() and unshift() builtins, maybe even a rotateLeft() and rotateRight(), could be useful in JS.
|
Hmm, not sure how this would be meaningful, given that JS "arrays" are really just pointers into a global memory space...
|
|
|
04-08-2012, 09:46 PM
|
#21
|
Human being with feelings
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
|
Quote:
Originally Posted by Justin
Actually I think I managed to speed up array accesses a bit on x86/x86-64, without changing function at all. I will post a new build soon with that (and stack support, which is mostly done but I need to implement on PPC).
For example, a stereo 16-sample bit-bucket style delay line using an array (buf[16]=buf[15]; buf[15]=buf[14]; ...) went from 1.7% CPU use to 1.2% CPU here, woot.
I also am going to look at adding some basic optimization passes, such as detecting expressions that can be resolved at compile time (such as converting x * 1 / 2 into x * 0.5, etc).
|
This is all awesome, thanks!! This will make all of my js FX run a lot better, I use arrays a fair bit.
|
|
|
04-09-2012, 08:11 AM
|
#22
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
|
This is great! While there's all this JS love in the air, how about that two's compliment ~ operator? A rebuild of ReaJS to bring it up to speed would be nice too .
|
|
|
04-09-2012, 09:17 AM
|
#23
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
Quote:
Originally Posted by IXix
This is great! While there's all this JS love in the air, how about that two's compliment ~ operator? A rebuild of ReaJS to bring it up to speed would be nice too .
|
To 32 bits, I guess? Or would it make more sense to make it be xor and require you to do (var~$xffffffff)?
Edit: this makes the most sense, I think. Perhaps a syntax for "n bits of 1" would be nice, like $~32.
Edit again: making ~ and ~= xor, and $~32 if you want to do 32 bits of 1s... so for example:
Code:
y = x ~ $~32;
y ~= $~32;
Almost ready for a new build, just need to implement the new stack functions on PPC, bleh..
Last edited by Justin; 04-09-2012 at 09:55 AM.
|
|
|
04-09-2012, 10:01 AM
|
#24
|
Human being with feelings
Join Date: May 2006
Location: Surrey, UK
Posts: 19,677
|
__________________
DarkStar ... interesting, if true. . . . Inspired by ...
|
|
|
04-09-2012, 11:27 AM
|
#25
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
Quote:
Originally Posted by DarkStar
|
You can use Ctrl+T or F4 for matching () or [] (if you want to see which parenthesis matches to which, etc).
|
|
|
04-09-2012, 01:35 PM
|
#26
|
Human being with feelings
Join Date: May 2006
Location: Surrey, UK
Posts: 19,677
|
Well, I never knew that ... any other goodies tucked away?
__________________
DarkStar ... interesting, if true. . . . Inspired by ...
|
|
|
04-09-2012, 02:56 PM
|
#27
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
Functions, a stack, array optimizations.. now THAT is some JS love, thank you Justin!
WOW!
@DS: didn't knew that one either!
|
|
|
04-09-2012, 02:59 PM
|
#28
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
Slow array..
I agree with remarks above (in @sample, you can even see the CPU use growing each time a line of code with an array access is added)
It's hard to shoot ideas since it really depends on how things are done behind the scene but one thing I always wondered is how the global array is allocated (?)
Because one thing that would be awesome is to delegate things to us: let us do the allocs/re-allocs so we could also manage (or not ;-) tied things like bounds checking. Optionnal? or with a new 3rd array "lmem"?..
EDIT: a stack will be cool, of course! but unless I misunderstand "peekstack 2", it would not be as flexible as an array.
Well, in short I mean I'm all for faster arrays
|
|
|
04-10-2012, 12:40 AM
|
#29
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
|
Any chance of a gfx_poly() function? It could reference the effect memory like gfx_blitex does, ie.
Code:
gfx_poly(numPoints, addressOfPoints, fillColor)
...where addressOfPoints would be the start of a list of x/y pairs. Outline could be drawn using the gfx_r/g/b vars, fill could be a packed rgb value.
I never got around to making a real FR for this but is there any point in doing so for JS requests? They never get many votes.
|
|
|
04-10-2012, 12:43 AM
|
#30
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
|
Quote:
Originally Posted by Justin
Edit again: making ~ and ~= xor, and $~32 if you want to do 32 bits of 1s... so for example:
Code:
y = x ~ $~32;
y ~= $~32;
|
Awesome!
|
|
|
04-12-2012, 01:16 PM
|
#31
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,721
|
OK I've updated the jsfx build linked in the original post of this thread. Some big updates are in this one, updates that will need a lot of testing. Here's the summary: - Vastly improved code generation (code is smaller and faster, a lot of excess instructions and housekeeping has been optimized out).
- High level optimizations -- constant expressions can be precalculated, division by constants can be converted into multiplies, ((2^x)^y) can be simplified into 2^(x*y), etc. Internally we changed the compiler to generate a tree of opcodes which can be quickly analyzed and reduced.
- Array (memory) addressing is a lot faster. The biggest difference here is actually in the PPC version, at least within Rosetta. If someone can test on a real PPC machine that would be very much appreciated.
- Added a fast, limited size (4096 slots) stack. stack_push(x) pushes. x=stack_pop(), or stack_pop(x) both pop. stack_peek() gets the value of the top item of the stack (and is very fast), stack_peek(1) gets the second item (and if it is passed a constant such as 0 or 1 or 2 etc it is also very fast), etc. stack_peek(-1) gets the next (unused) stack slot. stack_exch(x) exchanges x with the top of the stack.
- ~ operator added (bitwise xor), ~= for xor-op (x ~= 1 flips the lowest bit). Also added syntax for generating quick bitmasks, $~16 generates 65535, etc.
- Variable names can now be 64 characters (they were previously limited to 16 chars), and can have .'s within them (though they can't start with .).
- There is some initial support for some statically-bound, template-like object-orientedness. In addition to local() for functions, you can also specify a list of variables via instance(), and those variables names will be concatenated with the context of the function. I am not going to do a good job of explaining this right now, but I will give an example:
Code:
function someMethod(x)
instance(value, somestate) // specific to the prefix of the caller, or if no prefix, then it would be someMethod.somestate etc.
(
somestate = value*x;
);
obj1.value = 32;
obj1.someMethod(4);
obj2.value = 16;
obj2.someMethod(2);
v = obj1.somestate == 128 && obj2.somestate == 32;
// v should be true
It is not meant to be full OOP, but it does allow you to easily reuse code for different things, i.e. if you have a filter that you want to use in 4 places with different parameters, you can do that cleanly and efficiently...
Last edited by Justin; 04-14-2012 at 02:36 PM.
|
|
|
04-12-2012, 01:26 PM
|
#32
|
Human being with feelings
Join Date: Jul 2006
Location: Cowtown
Posts: 1,562
|
Very cool. Time for testingness.
Scott
|
|
|
04-12-2012, 01:39 PM
|
#33
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Nice developments for JesuSonic!
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
04-12-2012, 02:06 PM
|
#34
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
|
O.M.G.
I'll say it again, with added emphasis...
O! M! G!
|
|
|
04-12-2012, 02:29 PM
|
#35
|
Moderator
Join Date: Aug 2007
Location: Italy
Posts: 4,326
|
Since there's some JS love going on, any chance to be able to process data in advance for MIDI plugins without inducing PDC strain?
Plugins like my quantizer would benefit a lot from the above feature: I have a realtime quantize JS plugin that needs data be moved earlier in the arrange in order to be able to process data without needing big PDC (the plugin must be able to move data back and forth in time, so it needs data in advance).
IIRC, the needed amount of prebuffering is around 2 * quantize interval, which, in case of fairly big quantize intervals like 1/2 or 1/4 beats means a lot of unneeded sample prebuffering.
If MIDI only plugins could get data in advance by that kind of figures without inducing that big latency, that would help tremendously to improve the usability of MIDI plugins that deal with timing (ahem flat and groove quantizers ahem)...
Further details about my plugin: http://forum.cockos.com/showthread.php?t=42594
Thank you,
Mario
|
|
|
04-12-2012, 02:48 PM
|
#36
|
Human being with feelings
Join Date: Apr 2012
Posts: 4
|
Wow! This is incredibly cool. I was trying to use C preprocessor for this kind of semi-object-oriented stuff but 16 chars limit prevented me from using token pasting effectively. Just when I was considering to write my own preprocessor I saw this thread. And it all happened today!
Here's a small but useful js function, just to show that this new addition opens up some very cool possibilities that may not be immediately recognizable:
Code:
@init
function malloc(size)
local(result)
(
result = heapTop;
heapTop += ceil(size);
result;
);
// Allocate 200 ms delay buffers for each channel
delayBufferL = malloc(srate * 0.200);
delayBufferR = malloc(srate * 0.200);
// Report memory usage
freembuf(heapTop);
Automatic memory management for JS
One can also declare heapTop as an instance variable to avoid polluting the global namespace. I love it!
Now this addition begs for an include file functionality (in case it doesn't exist already), with a dedicated Effects\Include folder so we can immediately start rolling reusable DSP libraries for interpolated delay buffers, biquad filters etc.
Last edited by cyco130; 04-12-2012 at 04:11 PM.
Reason: Corrected freemembuf -> freembuf in the code
|
|
|
04-12-2012, 03:50 PM
|
#37
|
Human being with feelings
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
|
ok, this is awesome.
i'll start playing with this this weekend :-)
|
|
|
04-12-2012, 04:42 PM
|
#38
|
Human being with feelings
Join Date: Jan 2011
Location: Finger Lakes, NY
Posts: 54
|
I didn't do any actual testing, but a cursory tryout on my 1gHz G4 (OS v10.4.11; Reaper v4.21) does appear zippier. Case in point: a 1/f generator I've been tinkering with used to clog up MIDI and choke at high rates, but now spews notes with aplomb. Sweet.
|
|
|
04-12-2012, 06:21 PM
|
#39
|
Human being with feelings
Join Date: Apr 2012
Posts: 4
|
I've played a little with the new template/object system. How about a this keyword so a function can call another function with the current prefix? i.e.:
Code:
function helper()
instance(state)
(
// change the state in a way that's useful in more than one place
);
function f1()
instance(state)
(
this.helper();
// Do something
);
function f2()
instance(state)
(
this.helper();
// Do something else
);
object.f1(); // helper() will be called with "object." prefix.
This way one can refactor common code that operates on an object.
Currently calling helper() without a prefix from f1() operates on helper.state which is a little confusing. Maybe operating on the current prefix could be the default behavior so we can get rid of the this keyword? One could always call helper.helper() if the current behavior is desired.
|
|
|
04-13-2012, 02:51 AM
|
#40
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Justin
[*] There is some initial support for some statically-bound, template-like object-orientedness. In addition to locals() for functions, you can also specify a list of variables via instance(), and those variables names will be concatenated with the context of the function.
|
Very cool! I was just playing around with this a bit, but I got an error when declaring local variables using locals(). However, then I saw that cyco130 used local() instead of local s()... So, which is it, local() or locals()?
|
|
|
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 09:39 AM.
|