Go Back   Cockos Incorporated Forums > REAPER Forums > ReaScript, JSFX, REAPER Plug-in Extensions, Developer Forum

Reply
 
Thread Tools Display Modes
Old 07-22-2009, 09:06 AM   #1
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default JS function alternatives and useful snippets of code.

this is a thread in which you can contribute:


- alternatives to the default functions in jesusonic - approximations or simply "cheaper" algorithms, but less cpu extensive. share your methods in js code plus any links to original references. the default functions should be considered as the optimal base of comparison, regarding the quality / cpu trade-off.
- other code optimization techniques. - what is faster?
- any snippets of code, that may be useful to future coders.
- "dsp explained" notes - how does a filter work? how to do convolution?
- any interesting dsp related articles, books and lectures.




lubomir

Last edited by liteon; 07-22-2009 at 09:40 AM.
liteon is offline   Reply With Quote
Old 07-22-2009, 09:07 AM   #2
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default fast park–miller random number generator

a fast linear congruential generator (or specifically the park-miller variation) is described below.

basic algorithm:
Code:
@init
rnd=1;
@sample
rnd=rnd*a%m;
but the main problem is the range in our case, since with some of the common lcg parameters we go out of range:
Code:
@init
rnd=1;
@sample
rnd=rnd*16807%2147483647;
// call 0
// 1*16807%2147483647=16807
// call 1
// 16807*16807%2147483647=2824275249
// call 2
// 2824275249*16807%2147483647=1 -> out of range prevention
therefore a larger modulus cannot be used, such as the 2^31-1 -> 'marsenne prime'

so here are some parameters i've calculated based on the "zx-spectrum" multiplier,(77) but with much larger modulus.

Code:
//all with initial seed 1
rnd0*=77;                     //$x4D
rnd0%=16777219;               //$x1000003=2^24+3
rnd1*=76;                     //$x4C
rnd1%=16777221;               //$x1000005=2^24+5
rnd2*=77;                     //$x4D
rnd2%=8388609;                //$x800001=2^23+1
so basically all that is needed is a scaling factor in the end:
Code:
//full code
@init
rnd=1;
@sample
rnd*=77;
rnd%=16777219;
spl0=rnd*0.0000000593; //scale down to 0-1 range
comparison with the marsenne twister implementation in rand().
100 executions of rand() = 61% cpu
100 executions of one recursion lane lcg = 26% cpu

@gfx plots (ermm...30sec freerun)

two rand() x,y:

[img]http://img195.**************/img195/7922/mtwister.png[/img]

two recursions lanes lcg for x,y:

[img]http://img195.**************/img195/5992/gauss.png[/img]

the more expensive gaussian noise version as a bonus:
Code:
//gaussian version
@init
rnd0=rnd1=rnd2=1;
@sample
rnd0*=77;
rnd0%=16777219;
rnd1*=76;
rnd1%=16777221;
rnd2*=77;
rnd2%=8388609;
spl0=(rnd0+rnd1+rnd2)*0.00000002403333333333333; //.... *k
//k=(1/3*0.0000000721)

----
lubomir

Last edited by liteon; 07-25-2009 at 07:44 AM.
liteon is offline   Reply With Quote
Old 07-22-2009, 12:45 PM   #3
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default fast integer conversations

fast fractional to integer conversation for negative or positive input (x):

floor:
Code:
x|=0;
ceil:
Code:
x+=0.9999999999999999*sign(x);
x|=0;
round:
Code:
x+=0.5*sign(x);
x|=0;

---
lubomir

Last edited by liteon; 07-25-2009 at 04:15 AM.
liteon is offline   Reply With Quote
Old 07-22-2009, 12:50 PM   #4
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default video lectures on dsp

Prof. S.C Dutta Roy, Department of Electrical Engineering, IIT Delhi

43 - 1 hour lectures on DSP at:
https://www.youtube.com/view_play_lis...67DFCA3A66F299



----
lubomir
liteon is offline   Reply With Quote
Old 07-22-2009, 01:23 PM   #5
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default exponential frequency mapping

y range: 20-20000hz
---

x range: 0-127
Code:
y=(exp((16+x*0.945699)*0.0573250666192694)*8.17742)|0;
x range: 0-100
Code:
y=(exp((16+x*1.20103)*0.0573250666192694)*8.17742)|0;
x range: 0-1
Code:
//y=exp(x*log(20000/20)+log(20));
y=exp(x*6.90775527898214+2.99573227355399);
y|=0;
graphic:
http://img188.**************/img188/6396/expplot.png

----
lubomir

Last edited by liteon; 08-13-2009 at 07:43 PM.
liteon is offline   Reply With Quote
Old 07-22-2009, 02:28 PM   #6
cern.th.skei
Human being with feelings
 
cern.th.skei's Avatar
 
Join Date: Sep 2007
Location: trondheim, norway
Posts: 362
Default

a really cool initiative!
here's some snippets i've used in a few plugins:

a sin() approxamation for an oscillators, lfo, etc..

Code:
desc:sinusoid (approximation)
slider1: 55 < 1, 44100 > freq left  - sinusoid
slider2: 55 < 1, 44100 > freq right - sin()
@init
  p0 = 0;
  p1 = 0;
@slider
  r0 = (slider1/srate)*4;
  r1 = (slider2/srate);
@sample
// sinusoid
  p0+=r0;
  p0>2 ? p0-=4;
  s0 = p0*(2-abs(p0));
// sin()
  p1+=r1;
  p1>=1 ? p1-=1;
  s1 = sin(p1*($pi*2));
//
  spl0 = s0;
  spl1 = s1;
another random/noise thing

Code:
desc:noise/random
@init
  value   = 19; // initialize
  b_noise = 19.1919191919191919191919191919191919191919;
@sample
// -1..1
  b_noise = b_noise * b_noise;
  i_noise = floor(b_noise); // |0;
  b_noise = b_noise - i_noise;
  r0      = b_noise - 0.5;
  b_noise = b_noise + value;
// 0..1
  b_noiselast = b_noise;
  b_noise = b_noise + value;
  b_noise = b_noise * b_noise;
  b_noise = (b_noise+b_noiselast) * 0.5;
  i_noise = floor(b_noise);
  b_noise = b_noise - i_noise;
  r1      = b_noise;
//
  spl0 = r0;
  spl1 = r1;
and some filtering

Code:
desc:lowpass / weighted average
slider1:0.5<0,1,0.001>weight
@init
  n0 = 0;
  n1 = 0;
@slider
  weight = slider1*slider1;         // ???
@sample
  spl0 =  (n0+=((spl0-n0)*weight)); // lowpass
  spl1 -= (n1+=((spl1-n1)*weight)); // highpass
perhaps not scientifically correct, but useful when cpu and simplicity is important. the noise and sine things are around twice as fast as the builtin rand() and sin() functions.
__________________
torhelgeskei.com
cern.th.skei is offline   Reply With Quote
Old 07-22-2009, 03:06 PM   #7
Mich
Human being with feelings
 
Join Date: May 2009
Posts: 1,265
Default

Quote:
Originally Posted by cern.th.skei View Post
and some filtering

Code:
desc:lowpass / weighted average
slider1:0.5<0,1,0.001>weight
@init
  n0 = 0;
  n1 = 0;
@slider
  weight = slider1*slider1;         // ???
@sample
  spl0 =  (n0+=((spl0-n0)*weight)); // lowpass
  spl1 -= (n1+=((spl1-n1)*weight)); // highpass
perhaps not scientifically correct, but useful when cpu and simplicity is important.
The low/highpass is a perfectly correct RC filter as far as I can tell.

The formula to calculate the "weight" from frequency is:
weight = 1-exp(-2*$pi*fc/srate);
and for time (that is RC time constant) it is:
weight = 1-exp(-1/t); where t is the time it takes the filter to decay to 36.8% of its initial input or reach 63.2% of its final output.

P.S. liteon you accidentally have a seed of 0 in one of your examples.

Last edited by Mich; 07-22-2009 at 03:15 PM. Reason: I'm an idiot! :(
Mich is offline   Reply With Quote
Old 07-22-2009, 10:07 PM   #8
dub3000
Human being with feelings
 
dub3000's Avatar
 
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
Default

great thread, thanks!
dub3000 is offline   Reply With Quote
Old 07-23-2009, 02:24 AM   #9
cern.th.skei
Human being with feelings
 
cern.th.skei's Avatar
 
Join Date: Sep 2007
Location: trondheim, norway
Posts: 362
Default sample accuracy

another two snippets or examples, related to sample accuracy. you might need to tweak these to make them work in your own code..

if you want somethig to happen every x samples, for timing or sync, you can do something like this:

Code:
desc:sample accurate timing
@block
  size = beats * srate / (tempo/60);
  // transport change
  play_state != prevstate ? (
    play_state&1 ? countdown=0;//speed;
    prevstate = play_state;
  );
  // timing
  offset = 0;
  block = samplesblock;
  while(
    countdown>=block ? (
      countdown-=block;
      block=0;
    ) : (
      offset += countdown;
      block-=countdown;
      // ...do stuff here...
      //  ...or set some flags/variables...
      // ...offset = offset within current block (for midi out, etc)
      countdown=size;
    );
    block>0;
  );
and, one for handling midi events.. only one event per sample/offset here, but if you need more (notes?), you can stuff the _number_ of events per sample into the EVENTS buffer, and have another buffer where the actual events or data is stored, or have a 2d buffer and some indexing (offset*max_events_per_offset):

Code:
desc:sample accurate midi events
slider1: 60 <0,127,1> midi cc num
@init
  EVENTS = $x10000;
  i127 = 1/127;
  i256 = 1/256;
@slider
  midicc = slider1;
@block
  memset(EVENTS,-1,samplesblock); // ???
  while(
    midirecv(ofs,msg1,msg23) ? (
      msg = msg1 & 240;
      msg == (11*16) ? (
        cc = msg23 & 127;
        cc==midicc ? (
          val = (msg23*i256) & 127;
          EVENTS[ofs] = val*i127; // -> 0..1
        );
      );
      //midisend(ofs,msg1,msg23);
    );
  );
  offset = 0;
@sample
  EVENTS[offset]>=0 ? (
    our_value = EVENTS[offset];
  );
  EVENTS[offset] = -1;
  offset += 1;
  // ...use our_value for something
(let's hope my cut'n'paste didn't introduce too many errors or incompabilities)

- ccernn
__________________
torhelgeskei.com
cern.th.skei is offline   Reply With Quote
Old 07-23-2009, 02:50 AM   #10
Jeffos
Mortal
 
Jeffos's Avatar
 
Join Date: Dec 2008
Location: France
Posts: 1,969
Default

Yes, great tips here!
So here are some (more basic :-) things I have noted. HTH... Here I focus on:
Quote:
Originally Posted by liteon View Post
- other code optimization techniques. - what is faster?
How to know what is faster (i.e. how to monitor the CPU) ?
We cannot trust Reaper's performance meter (it seems it does not take some things into account, such as the @gfx thread). So, what I do: I set up a test project, I clean it as much as possible (usually one media item + the js). Then, I monitor reaper with Win's performance meter (cool for threads but I also simply use the task manager when I'm in an rush) with DISABLED JS, then I just enable it. The CPU use is the diff (note: diff done in the same conditions - just enabling the JS - as play/routing/etc.. have an impact), dif made with hidden and shown GUI.
I'd like to know how other js coders monitor CPU/RAM !?

Where to code ?
This one is really basic - I know - but it must be said! I often see that and it leads to the best optimizations => put the maximum of code in the @block part (as far as possible: typically periodic processing NOT needed for each sample), and, of course, the less in the @sample.
Then, you can even lower the processing frequency, e.g.:
Code:
@block
cpt+=samplesblock;
(cpt - lastCpt) > updatefreq ?
(
   lastCpt=cpt;
   // do some heavy stuff
);
where "updatefreq" is in samples *
[edit] *if computed: only when needed! i.e. in the @init or @slider parts.

Branching
Huge impact on CPU.
=> avoid them as far as possible!

=> use logical operator precedence
Code:
cond2 = cond1 || (floor(rand(spl0) * $pi * 1000) % 2);
cond2 = cond1 && (floor(rand(spl0) * $pi * 1000) % 2);
will be faster than:
Code:
cond2 = (floor(rand(spl0) * $pi * 1000) % 2) || cond1;
cond2 = (floor(rand(spl0) * $pi * 1000) % 2) && cond1;
here, floor() & rand() are used for test purpose only

=> some code branching tips
The 2 following code lines do the same thing (where "cond1" is a "boolean"):
Code:
//1)
spi1 = condi1 * slider1 * spl0;
//2)
condi1 ? spi1 = slider1 * spl0 : spi1 = 0;
According to some (old) Justin's post, I was thinking that 1) was the fastest one. In fact, CPU monitoring shows that that fastest one is 2) (Reaper v3.06pre7).
I really don't understand why! If a dev read this post, can he just confirm/say why (very briefly) ?

=> setting a default STATIC value according to a condition
Code:
a=0; cond1 ? a = floor(rand(spl0) * $pi * 1000);
is faster than:
Code:
cond1 ? a = floor(rand(spl0) * $pi * 1000) : a=0;
Midi processing
=> counting samples (basic thing, but I've also seen this possible optimization in a bunch of JS!). We often have to evaluate time elapsed between midi events, for this we have to count samples.
Code:
@block
cpt+=samplesblock; // +use midircv()'s 1st param for sample accurate processing
is faster than:
Code:
@sample
cpt+=1;
[Edit] even more true since v3.06!

coming back later...

Last edited by Jeffos; 07-23-2009 at 01:33 PM. Reason: english wording + better with "compilable" code !
Jeffos is offline   Reply With Quote
Old 07-24-2009, 05:44 AM   #11
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default fast polynomial approximations of exp()

fast approximations of exp() with lagrange polynomials:

Code:
//stable range for x is [-1.5, 1.5]

//3 points polynomial
//y=x*(x*0.601+1.42)+1;
y=x;
y*=0.601;
y+=1.42;
y*=x;
y+=1;

//4 points polynomial
//y=x*(x*(x*0.189+0.612)+0.995)+0.975;
y=x;
y*=0.189;
y+=0.612;
y*=x;
y+=0.995;
y*=x;
y+=0.975;

//5 points polynomial
//y=x*(x*(x*(x*0.046+0.191)+0.498)+0.989)+1;
y=x;
y*=0.046;
y+=0.191;
y*=x;
y+=0.498;
y*=x;
y+=0.989;
y*=x;
y+=1;
plot comparisons with exp():

[img]http://img233.**************/img233/4125/explagrangen.gif[/img]

performance test - 100 executions per sample:
4 point -> 26% cpu
exp(x) -> 56% cpu

example:
Code:
//omega=2*$pi*fc=2*$pi*2000=12566.370614
//x=-omega/srate=-omega/44100=
x=-0.284951;
y0=exp(x);                              //0.7205110
y1=x*(x*(x*0.189+0.612)+0.995)+0.975;   //0.7367934
---
lubomir
liteon is offline   Reply With Quote
Old 07-24-2009, 06:08 AM   #12
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default fast pow() from identity

using the above approximation of exp() and the identity:
Code:
2^x=exp(x*log(2));
it is possible to get a fixed base approximation of b^x:
Code:
@init
//base 2
b=log(2);
@sample
//example with 4 point polynomial:
x*=b;
y=x;
y*=0.189;
y+=0.612;
y*=x;
y+=0.995;
y*=x;
y+=0.975;

//x=1 -> y=2.02166035;
//x=2 -> y=4.03404514;
stable within x range [-2, 2,5]

[img]http://img233.**************/img233/9909/powlagrange.gif[/img]

performance test (100 executions per sample):
4point approximation (2^x) - 33%
inline 2^x - 75%

---

lubomir
liteon is offline   Reply With Quote
Old 07-24-2009, 07:40 AM   #13
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default fractional delay approximation with an allpass filter

fractional delay approximation with an allpass filter:

Code:
//code here
@init
k=0.5;
@sample
y0=spl0-k*(y=y0+k*spl0);
spl0=y;
quick explanation:
the above is a basic one pole allpass filter. in one sentence - allpass filters affect only the phase of the signal while preserving the energy (magnitude) of the signal. this way it is possible to get relative group delay, the amount of which is dependent from a pole position (k). note that group delay value does not correspond directly to the pole i.e. k=0.5 != gd=0.5 samples. this requires extra mapping for user controlled amounts.

[img]http://img233.**************/img233/6681/gdmapping.gif[/img]

example for parameter mapping with an exponent:
Code:
//error is minimized but k should be limited to [0,1] for slider values [0,1]:
slider1:0<0,1,0.01>d
@slider
x=slider1;
k=exp(x*1.29)/$e-0.356;


various fractional delay methods described here:
http://sal.shs.arizona.edu/~smathur/.../MathurCh3.pdf
http://www.acoustics.hut.fi/~vpv/pub...cassp00-fd.pdf



---
lubomir

Last edited by liteon; 07-25-2009 at 02:54 AM.
liteon is offline   Reply With Quote
Old 07-24-2009, 09:48 AM   #14
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default code optimizations

this syntax is troublesome for the given function due to the 'pow' operator:
Code:
y=7*x^4+5*x^3-3*x^2+2*x-1;
this is better:
Code:
y=7*x*x*x*x+5*x*x*x-3*x*x+2*x-1;
even better:
Code:
y=x;
y*=7;
y+=5;
y*=x;
y-=3;
y*=x;
y+=2;
y*=x;
y-=1;
best:
Code:
y=x*(x*(x*(7*x+5)-3)+2)-1;
-----------------------------------------------
however it is slightly faster to execute:
Code:
x=6.28;
x*=2;
x|=0;
x%=3;
over:
Code:
x=6.28;
x=((2*x)|0)%3;
-----------------------------------------------
if there are more variables and recursion:
Code:
@sample
y=y0+k*spl0;
y0=spl0-k*y;
spl0=y;
could be optimized to:
Code:
y0=spl0-k*(y=y0+k*spl0);
spl0=y;
-----------------------------------------------
loops:

loop() - 73% cpu
Code:
i=0;
loop(100,
mem[i]=exp(i);
i+1;
);
while() - 76% cpu
Code:
i=0;
while(
mem[i]=exp(i);
i+=1;
i<100; //<- inlined cmp - eventual cause for 76%
);
-----------------------------------------------
min() / max() or inline cmp:

Code:
x=0.9;
//this call
x<1?x=1;
//is slower than
x=max(x,1);
-----------------------------------------------
abs() and sign() are both quite fast:
correction here:
Code:
//alternative to abs()
x=-0.5;
x*=sign(x);
//700 iterations, x=-0.5, x=abs(x) -> 82.6% cpu, x*=sign(x) -> 84.5% cpu
//700 iterations, x=0.5, x=abs(x) -> 80.1% cpu, x*=sign(x) -> 76.5% cpu
//--
//if input values are mostly with negative sign use abs(x)

---
lubomir

Last edited by liteon; 03-28-2011 at 10:40 PM.
liteon is offline   Reply With Quote
Old 07-24-2009, 12:55 PM   #15
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default using lookup tables

for expensive functions, lookup tables (lut) can be used, so that the values are pre-computed in indexes (@init section) and only certain indexes are called in realtime (@sample section). results from using lut - are great accuracy and fast execution times.

here is an example with the log() function:

Code:
@init
//set depth of 256 indexes
depth=2^8;
i=0;
loop(depth,
//add value
ln[i]=log(i/depth);
i+=1;
);
@sample
//possible x range in this case [0,1]
x=0.5;
//multiply input to depth.
x=ln[x*depth]; // <-- the fractional part is discarded by the compiler
some outputs for the above:
Code:

depth is: 256
---
input is: 0.1
>> call index: 25
>> log(x): -2.30258509299405
>> from lut: -2.32630161961136
---
input is: 0.5
>> call index: 128
>> log(x): -0.693147180559945
>> from lut: -0.693147180559945
---
input is: 1
>> call index: 256
>> log(x): 0
>> from lut: 0
performance:

- in the form of:
Code:
x=0.5;
y=x;
y*=depth;
loop(100,
y=ln[y];
);
cpu usage is 35%
compared to 57% for 100 calls of x=log(x)

notes:
- bigger accuracy is achieved with bigger scaling factor 'depth' (and more indexes accordingly).
- if not enough accuracy - lower index is returned. example:
Code:

depth is: 256
---
input is: 0.01
>> call index: 2
>> log(x): -4.60517018598809
>> from lut: -4.85203026391962
---
input is: 0.001
>> call index: 0
>> log(x): -6.90775527898214
>> from lut: -Infinity
- a simple modification is required for negative inputs values (as there are no negative indexes) or different ranges other than [0,1]. in the case of ln[] negative indexes will return 0, which is acceptable.
- this method can be used for many functions, such as exp(), rand(), sin(), b^x and etcetera.



---
lubomir

Last edited by liteon; 08-22-2009 at 04:17 PM.
liteon is offline   Reply With Quote
Old 07-28-2009, 04:48 AM   #16
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default oversampling with "zero-stuffing" method

"oversampling" in dsp therms is "processing at a higher sample rate, then restoring to the original sample rate" or related to a dsp effect which is "oversampled". The most common use is in cases, where effects produce the reflection like artifact of "aliasing" (or static distortion). Nx oversampling requires execution of the same process Nx times.

a 4x os scheme for a non-recursive [fx]:
usf - up-sample
dsf - down-sample
bl - band-limit (aliasing reduction or anti-aliasing)
Code:
            -->- [fx0] [bl] -->-
          /                      \
      [usf]                      [dsf]
      /   \                      /    \
     /      -->- [fx1] [bl] -->-       \
    /                                   \ 
[usf]                                   [dsf]
    \                                   /
     \      -->- [fx2] [bl] -->-       / 
      \   /                      \    /
      [usf]                      [dsf]
          \                      / 
            -->- [fx3] [bl] -->-
the above scheme is only for a non-recursive fx (not using previous sample values) in the lines of y(n)=sin(x(n)).
for a recursive fx in the lines of y(n)=sin((x(n-1)+x(n))*0.5), the following must be done:
one should think of the two outputs of an up-sample filter as a sequence in a discrete signal: out0 is followed by out1, but to make an recursive fx work, you should feed the result of processing of out0 into fx1, since out0 is previous to out1. and then for the next call the processed output of out1 should be fed into fx0.

if we block-diagram this:
Code:
       --[out0] ->--------- [fx0] -- [result0] ->- ...
     /    ...(result1) ->----/
[usf]  
     \ 
       --[out1] ->--------- [fx1] -- [result1] ->- ...
          ...(result0) ->----/
a triangular window filter as an example:
Code:
//usf
usout0=(usout1+input)*0.5;
usout1=input;

//fxout0 = process -> usout0;
//fxout1 = process -> usout1;

//bandlimit -> fxout0;
//bandlimit -> fxout1;

//dsf
output=0.25*(dsmem0+2*dsmem1+fxout1);
dsmem0=fxout0;
dsmem1=fxout1;
notes:
- up/down sampling introduces aliasing of its own.
- the quality of the up/down sample and band-limit filters is of great importance (high order filters required). the above triangular filter requires "signal restoration" in the form of high-shelf boost or similar since it also affects the audible range.
- oversampling ends up as a resource expensive process, so it is best to be avoided, unless more resources are available.

extended information:
http://ccrma.stanford.edu/~jos/resample/resample.html (by jos - ccrma @ stanford)
http://synthmaker.co.uk/dokuwiki/dok...s:oversampling (by andrew j - sm)


---
lubomir

Last edited by liteon; 08-01-2009 at 08:09 PM.
liteon is offline   Reply With Quote
Old 08-20-2009, 07:12 AM   #17
Fabian
Human being with feelings
 
Fabian's Avatar
 
Join Date: Sep 2008
Location: Sweden
Posts: 7,416
Default MIDI-only FX, in/out_pin:none

This is from JS:MIDI/midi_transpose. I had no idea, but it seems to make sense.

Code:
// these lines tell Reaper the effect has no audio input/output,
// which enables processing optimizations.
// MIDI-only FX should always have these lines.
in_pin:none
out_pin:none
__________________
// MVHMF
I never always did the right thing, but all I did wasn't wrong...
Fabian is offline   Reply With Quote
Old 08-20-2009, 08:16 AM   #18
Justin
Administrator
 
Justin's Avatar
 
Join Date: Jan 2005
Location: NYC
Posts: 15,721
Default

Quote:
Originally Posted by Jeffos View Post
=> some code branching tips
The 2 following code lines do the same thing (where "cond1" is a "boolean"):
Code:
//1)
spi1 = condi1 * slider1 * spl0;
//2)
condi1 ? spi1 = slider1 * spl0 : spi1 = 0;
According to some (old) Justin's post, I was thinking that 1) was the fastest one. In fact, CPU monitoring shows that that fastest one is 2) (Reaper v3.06pre7).
I really don't understand why! If a dev read this post, can he just confirm/say why (very briefly) ?
I think it depends on what condi1 refers to.. If it's mostly 0, then 2) should be faster. Other cases, the difference becomes less I'd think.
Justin is offline   Reply With Quote
Old 08-27-2009, 08:34 AM   #19
IXix
Human being with feelings
 
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
Default Debugging Aid

Great thread! I can't contribute much but I'll re-post this (slightly improved) snippet here where more people might find it.

Just paste this at the bottom of your @gfx section to display a chunk of memory buffer and six general purpose debugging variables. You can set various parameters in the options bit at the top.

Code:
/*******************************************************************************
    General purpose debugging aid. Paste into the gfx section of your code.

    Variables:
    debug1...debug6      - Displayed at the top of the debug area.
    dOffset              - Index of first item of buffer data to display.

*******************************************************************************/

SHOWDEBUG = 1; //Set non-zero to enable the debug view

SHOWDEBUG ?
(    
    //Options
    dPrecision = 2;             //Decimal places for numbers
    dLeft = 5;                  //Debug window position in pixels
    dTop = 5;                   //Debug window position in pixels
    dRows = 5;                  //Row count for buffer data
    dCols = 6;                  //Column count for buffer data

    dRowHeight = 20;            //Row height offset for buffer data
    dColWidth = 80;             //Column width offset for buffer data
    dMarginX = 5;               //Left margin in pixels
    dMarginY = 5;               //Top margin in pixels
    dBufferY = 30;              //Vertical offset for buffer data display
    
    /* ------------------------------------------------------------- */

    dOld_x = gfx_x;
    dOld_y = gfx_y;
    dOld_a = gfx_a;
    dOld_r = gfx_r;
    dOld_g = gfx_g;
    dOld_b = gfx_b;
    dOld_mode = gfx_mode;

    gfx_a = 1;
    gfx_mode = 0;

    //Calculate debug window size
    dWidth = dCols * dColWidth + dMarginX;
    dHeight = dRows * dRowHeight + dBufferY;

    //Set background colour
    gfx_r=1;gfx_g=1;gfx_b=1;gfx_a=1;

    //Erase
    gfx_x = dLeft;
    gfx_y = dTop;
    gfx_rectto(dLeft + dWidth, dTop + dHeight);

    //Set text colour
    gfx_r=.7;gfx_g=0;gfx_b=.3;

    //Draw debug vars
    gfx_x = dLeft + dMarginX; gfx_y = dTop + dMarginY;
    gfx_drawNumber(debug1, dPrecision);

    gfx_x = dLeft + dMarginX + dColWidth; gfx_y = dTop + dMarginY;
    gfx_drawNumber(debug2, dPrecision);

    gfx_x = dLeft + dMarginX + dColWidth * 2; gfx_y = dTop + dMarginY;
    gfx_drawNumber(debug3, dPrecision);

    gfx_x = dLeft + dMarginX + dColWidth * 3; gfx_y = dTop + dMarginY;
    gfx_drawNumber(debug4, dPrecision);

    gfx_x = dLeft + dMarginX + dColWidth * 4; gfx_y = dTop + dMarginY;
    gfx_drawNumber(debug5, dPrecision);

    gfx_x = dLeft + dMarginX + dColWidth * 5; gfx_y = dTop + dMarginY;
    gfx_drawNumber(debug6, dPrecision);

    //Draw separator
    gfx_x = dLeft + dMarginX;
    gfx_y = dTop + dBufferY - dMarginY;
    gfx_lineto(dLeft + dWidth - dMarginX, gfx_y, 1);

    //Draw buffer data
    di = 0;
    dRow = 0;
    loop
    (
        dRows,
        dCol = 0;
        loop
        (
            dCols,
            gfx_x = dLeft + dMarginX + dCol * dColWidth;
            gfx_y = dTop + dMarginY + dBufferY + dRow * dRowHeight;
            gfx_drawNumber(dOffset[di], dPrecision);
            di += 1;
            dCol += 1;
        );
        dRow += 1;
    );

    //Restore previous state
    gfx_x = dOld_x;
    gfx_y = dOld_y;
    gfx_a = dOld_a;
    gfx_r = dOld_r;
    gfx_g = dOld_g;
    gfx_b = dOld_b;
    gfx_mode = dOld_mode;
);
You can use debug1...debug6 to report the value of a variable at specific points in your code, or they can be handy for tracing which parts of a conditional block are being executed. To use the buffer viewer, just set dOffset to the memory index you're interested in.

I've found it very helpful for dealing with my typically over-complicated scripts.

Last edited by IXix; 08-27-2009 at 08:45 AM. Reason: Added code to restore gfx variables to their original state after debug drawing is completed
IXix is offline   Reply With Quote
Old 08-28-2009, 09:35 AM   #20
Jeffos
Mortal
 
Jeffos's Avatar
 
Join Date: Dec 2008
Location: France
Posts: 1,969
Default

Hey! IXix! I use it, I forget to thank you for that in the other thread! Very handy especially in comparison with how I was debugging arrays before

Quote:
Originally Posted by Justin View Post
I think it depends on what condi1 refers to.. If it's mostly 0, then 2) should be faster. Other cases, the difference becomes less I'd think.
Thanks for the feedback, Mr Frankel! The "boolean" "cond" meant a variable that's either 0 or 1 and nothing else. So, in both cases, I was hoping some stuff not to be evaluated in "cond * something_else" as the result is either 0 or something_else.
Here's the not-good-for-speakers code I used to test that (where "cond" is often updated as you can see):
Code:
desc:Jeff test 27
slider1:0<0,1,1{1,2}>Mode
@init
cond=0;

@sample
cond = !cond;

!slider1 ? 
// 1)
(spl0 = cond * floor(rand(spl0) * $pi * 100);)
// 2)
: (cond ? spl0 = floor(rand(spl0) * $pi * 100) : spl0 = 0;);
As logical operations use operator precedence, I also tried "spl0 = cond && (floor(rand(spl0) * $pi * 100))" that is to say "&&" instead of "*" but it's ko: logical operations clamp results to 0 or 1 (edit: and that's fine)

Last edited by Jeffos; 08-28-2009 at 12:17 PM.
Jeffos is offline   Reply With Quote
Old 08-28-2009, 05:22 PM   #21
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
Default General JS synth implementation

I don't have any general optimizations, nor any ingenius DSP code snippets, but I do have a polyphone JS synth implementation. It receives MIDI notes in full polyphony (within the chosen note range), stores the notes in a buffer, and then caches this buffer for optimal speed. In this case sound is generated using a bunch of sinuses (with the frequencies for each note pre-calculated to speed up things a litle more), but you could easily replace that with any other sound generating code snippet.

Code:
// Sinas v0.0.1
// (c) 2009 Theo Niessink <http://www.taletn.com/>
// License: GPL <http://www.gnu.org/licenses/gpl.html>

desc:Simple stacked sine synth

in_pin:none

out_pin:Mono
out_pin:Mono


@init

bot_key = 36; // C2
top_key = 96; // C7
num_key = top_key - bot_key + 1;

// Pre-calculate the frequency for each key.
//freq_buf = 0;
key = 69 - bot_key; // A @ 440 Hz

// Calculate the top octave.
i = num_key;
loop(12, freq_buf[i -= 1] = 440.0 * 2 ^ ((i - key) / 12));

// Caclulate the lower octaves by dividing down the top octave.
loop(i, freq_buf[i -= 1] = 0.5 * freq_buf[i + 12]);

key_buf = freq_buf + num_key;
//memset(key_buf, 0, num_key);

cache_buf = key_buf + num_key;
//num_cache = 0;

pi2 = $pi * 2;
speriod = 1/srate * pi2;
//position = 0.0;


@block

while(
  midirecv(ofs, msg1, msg23) ? (
    event = msg1&$xF0;
    velocity = msg23&$x7F00;

    // Note On
    event == $x90 && velocity ? (
      key = msg23&$x007F;

      key >= bot_key && key <= top_key ? (
        key -= bot_key;

        // Add the key to the cache.
        key_buf[key] == 0 ? (
          cache_buf[num_cache] = key;
          num_cache += 1;
        );

        // Set the velocity value / (127 * 256) in the keyboard buffer.
        key_buf[key] = 0.000030757874015748031496062992125984 * velocity;
      );
    ) :

    // Note Off
    (event == $x80 || (event == $x90 && !velocity)) ? (
      key = msg23&$x007F;

      key >= bot_key && key <= top_key ? (
        key -= bot_key;

        // Remove the key from the cache.
        loop(i = num_cache,
          cache_buf[i -= 1] == key ? (
            num_cache -= 1;
            i < num_cache ? memcpy(cache_buf + i, cache_buf + i + 1, num_cache - i);
          );
        );

        // Clear the velocity value in the keyboard buffer.
        key_buf[key] = 0;
      );
    ) :

    // Control Change
    event == $xB0 ? (
      cc = msg23&$x007F;

      // All Notes Off
      cc >= 123 && (num_cache > 0 || position > 0.0) ? (
        memset(key_buf, 0, num_key);
        num_cache = 0;
        position = 0.0;
      );
    );

    midisend(ofs, msg1, msg23);
    1; // Loop
  );
);


@sample

spl0 = 0.0;

// Calculate a sample for each of the keys in the cache.
loop(i = num_cache,
  key = cache_buf[i -= 1];
  f = position * freq_buf[key];
  spl0 += key_buf[key] * (sin(f) + sin(2 * f) + sin(3 * f) + sin(4 * f) + sin(5 * f));
);

spl0 *= 0.1;
spl1 = spl0;

position += speriod;
num_cache == 0 && position >= pi2 ? position -= pi2;
Without the cache the sound generating loop in the @sample section would look something like this:

Code:
loop(key = num_key,
  velocity = key_buf[key -= 1];
  velocity > 0 ? (
    f = position * freq_buf[key];
    spl0 += velocity * (sin(f) + sin(2 * f) + sin(3 * f) + sin(4 * f) + sin(5 * f));
  );
);
This would make the @sample section considerably slower, especially in idle or with only a few keys playing. On my system the cached version takes 0.3% CPU time in idle, and 3.3% with 3 keys playing. The non-cached version takes 6.4% in idle, and 9.0% with 3 keys playing.
Tale is offline   Reply With Quote
Old 09-01-2009, 06:30 PM   #22
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default methods of pseudo-random number generation with non-uniform distribution

explained below is a method for generating white noise with non-uniform distribution.

unlike the gaussian function for white gaussian noise this method uses trigonometric functions and in general can be tweaked to produce quick variations in regard of probability density with relatively fast execution times.

you may have stumbled on this before doing a simple oscillator:
Code:
//full code
@init
x=y=1;
@sample
y=sin((x+=1)*y);
this recursion algorithm is a prng with non-uniform (trigonometric) distribution which outputs in the [-1,+1] range for y using sin(), previous value y[n-1] and an 'index' variable x.

probability density function:
sin() will result in a 'negative' pdf function (less density in center ), while tan() in positive.

execution times:
-slower than the above park–miller and faster than rand() (but both implementations are uniform).
-using tan() is slower than sin(), and tan() requires limiting and some tweaks.

use of approximations:
-example link with some approximations for trigonometric functions at musicdsp - http://musicdsp.org/files/approx.h

plot for tan() - scaled:


vid:
https://stash.reaper.fm/oldsb/618700/...tribution0.gif
extra points for guessing why the @gfx sections outputs the exponential grid.

plotter algorithm:
https://stash.reaper.fm/oldsb/618769/prngplot

short read on trigonometry in non-uniform distributions:
http://www.stat.wisc.edu/~larget/math496/random2.html



------
lubomir

Last edited by liteon; 09-01-2009 at 07:30 PM.
liteon is offline   Reply With Quote
Old 09-03-2009, 03:43 PM   #23
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default fm/rm/am

a post which summarizes some modulation methods (fm, rm, am), can be found here:

http://forum.cockos.com/showpost.php...34&postcount=7
(also an optimized square wave is included.)

--
lubomir
liteon is offline   Reply With Quote
Old 09-03-2009, 05:13 PM   #24
whatsup
Human being with feelings
 
whatsup's Avatar
 
Join Date: Aug 2008
Posts: 1,144
Default

Quote:
Originally Posted by Justin View Post
I think it depends on what condi1 refers to.. If it's mostly 0, then 2) should be faster. Other cases, the difference becomes less I'd think.
or maybe it because, it take less time to compare, than to mul ?

EDIT: reffers to that:

cond1 = 0 or 1

Quote:

Code:

//1)
spi1 = condi1 * slider1 * spl0;
//2)
condi1 ? spi1 = slider1 * spl0 : spi1 = 0;
__________________
you can contack me on
whatsup@inn.co.il

Last edited by whatsup; 09-03-2009 at 05:15 PM.
whatsup is offline   Reply With Quote
Old 09-09-2009, 12:25 PM   #25
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default precision

for evaluation there are 5 decimal places of precision.
Code:
@sample
a = 5*10^-5; //0.00005
b = 4*10^-5; //0.00004
//(a == b) = 1;
a = 5*10^-4; //0.0005
b = 4*10^-4; //0.0004
//(a == b) = 0;
justin suggests:
Quote:
Originally Posted by Justin
Note (for the docs), if you need more accuracy, you could do a*1000000 == b*1000000
-----
normally the accuracy of mathematical identities is dependent from the implemented functions (if are based on standards such as IEEE) and also very importantly the precision of the format.

evaluating the identity x=exp(log(x)) in c:
Code:
x is 5 (single or double) 
y=exp(log(x))
* for single precision:
x=5.0, y=5.000000000000000000000
(x==y)=true
* for double precision:
x=5.0, y=4.999999999999999100000
(x==y)=false
in js (double precision):
Code:
x=5;
y=exp(log(x));
//x 5.000000...
//y 5.000000... <- js debugger rounds value
x*=10^7;
y*=10^7;
//x 50000000.000000...
//y 49999999.999999...
assuming that the used mathematical functions are standardized this a trick that can be used to determine the precision in programming languages with less strict variable type definitions.

---
lubomir
liteon is offline   Reply With Quote
Old 12-01-2009, 09:21 PM   #26
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default jsfxgen

spamming a little..

JSfxGEN is a modular ide for jesusonic:



http://forum.cockos.com/showthread.php?t=46286

--
liteon is offline   Reply With Quote
Old 12-12-2009, 03:01 AM   #27
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default using memory slots

http://forum.cockos.com/showthread.p...d=1#post421220


--
liteon is offline   Reply With Quote
Old 02-11-2010, 07:49 PM   #28
liteon
Human being with feelings
 
liteon's Avatar
 
Join Date: Apr 2008
Posts: 510
Default calculating RMS for continous waveforms

some info here:
http://forum.cockos.com/showthread.php?t=51621

make sure you check the provided JesuSonic plugins (names) for code examples.

--

Last edited by liteon; 02-11-2010 at 07:57 PM.
liteon is offline   Reply With Quote
Old 02-11-2010, 08:59 PM   #29
dub3000
Human being with feelings
 
dub3000's Avatar
 
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
Default

thanks again, liteon. these posts are really useful. still love your JS plugins as well, the moog filter and waveshaper end up on everything!
dub3000 is offline   Reply With Quote
Old 03-07-2010, 03:34 AM   #30
zorn
Human being with feelings
 
Join Date: Feb 2008
Location: france
Posts: 230
Default

not a useful snippet, here's just a JS language file for Notepad++.
could be improved, the main interest is being able to fold text ( /* or //@ starts a a block of text and */ or //- closes it )
how to install : http://notepad-plus.sourceforge.net/uk/download.php
https://stash.reaper.fm/oldsb/814716/...nguageFile.txt

Last edited by zorn; 03-07-2010 at 04:56 AM.
zorn is offline   Reply With Quote
Old 06-12-2010, 09:38 AM   #31
magnus0re
Human being with feelings
 
Join Date: Jan 2010
Location: Bergen, Norway.
Posts: 78
Default

Quote:
Originally Posted by zorn View Post
not a useful snippet, here's just a JS language file for Notepad++.
could be improved, the main interest is being able to fold text ( /* or //@ starts a a block of text and */ or //- closes it )
how to install : http://notepad-plus.sourceforge.net/uk/download.php
https://stash.reaper.fm/oldsb/814716/...nguageFile.txt
Awesome, but how do i add the custom language to notepad++ ?
magnus0re is offline   Reply With Quote
Old 06-12-2010, 11:48 AM   #32
Fabian
Human being with feelings
 
Fabian's Avatar
 
Join Date: Sep 2008
Location: Sweden
Posts: 7,416
Default

Quote:
Originally Posted by magnus0re View Post
Awesome, but how do i add the custom language to notepad++ ?
You could start by googeling.
http://sourceforge.net/apps/mediawik...ghting_Sharing
http://sourceforge.net/apps/mediawik...ined_Languages
etc...
__________________
// MVHMF
I never always did the right thing, but all I did wasn't wrong...
Fabian is offline   Reply With Quote
Old 06-12-2010, 03:49 PM   #33
magnus0re
Human being with feelings
 
Join Date: Jan 2010
Location: Bergen, Norway.
Posts: 78
Default

Quote:
Originally Posted by Fabian View Post
You could start by googeling.
Yeah, I tried to do that, and did that. does not work for me apparently.
magnus0re is offline   Reply With Quote
Old 07-05-2010, 06:10 AM   #34
IXix
Human being with feelings
 
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
Default Synchronised MIDI generator template

Thought I'd give my karma a boost by sharing this. It's well commented, easy to read and includes my graphical debugging aid, so it should be useful to beginners.

IX MIDI Generator Template

It's a sort of template for a host synchronised MIDI generator. You could probably use it to build something like a drum machine or it would be easy to strip out the MIDI parts and just use the underlying synchronisation. Not sure if it's sample accurate but the timing is solid.

Edit: 2010.07.07 Oops, fixed incorrect slider definitions! Remember children, always test before you post.

Last edited by IXix; 07-07-2010 at 07:20 AM.
IXix is offline   Reply With Quote
Old 11-17-2010, 04:29 AM   #35
IXix
Human being with feelings
 
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,889
Default

Made a slightly better version of the above. https://stash.reaper.fm/v/7106/IX%20M...plate%20II.zip

Swingin'!
IXix is offline   Reply With Quote
Old 02-26-2011, 08:31 AM   #36
groundhum
Human being with feelings
 
groundhum's Avatar
 
Join Date: Jan 2011
Location: Finger Lakes, NY
Posts: 54
Default a bit of noob advice

Quote:
Originally Posted by IXix View Post
Made a slightly better version of the above. https://stash.reaper.fm/v/7106/IX%20M...plate%20II.zip
EDIT: Thanks, IXix, for the template. It saved me a great deal of wailing and gnashing of teeth. But the thrill of figuring out what stupid thing I'd done overwhelmed my sense of decency and good manners, and I forgot to note that originally.


Because I've been watching variables until my eyeballs are ready to bleed, might I suggest some small changes and/or additional commentary in lines 273-278 regarding dynamic note and velocity values?

If line 274 is changed to e.g.
Code:
velocity = rand(90)+30;
note is stomped upon.
Code:
velocity = (rand(90)+30) & 127;
avoids this unpleasant and time-consuming circumstance. And if the expression for note is likewise masked in line 273
Code:
note = (rand(36)+36) & 127;
for example, then lines 277 & 278 become redundant. Trust me; I know these things to be true. Now, at least…

Here's hoping this helps some other intrepid JS novice,
m.
__________________
Michael Rees, composer &c.
extraneous data

Last edited by groundhum; 02-26-2011 at 08:47 AM. Reason: bad manners
groundhum is offline   Reply With Quote
Old 03-23-2011, 01:17 PM   #37
caseyjames
Human being with feelings
 
caseyjames's Avatar
 
Join Date: Jul 2009
Posts: 632
Default

Quote:
Originally Posted by IXix View Post
Made a slightly better version of the above. https://stash.reaper.fm/v/7106/IX%20M...plate%20II.zip

Swingin'!
How hard would it be to implement a function to read-in small midi files to use as sequences? Has anyone done anything like that with this yet?

This seems like a very useful template for future projects.
caseyjames is offline   Reply With Quote
Old 03-24-2011, 08:33 AM   #38
Mich
Human being with feelings
 
Join Date: May 2009
Posts: 1,265
Default

Quote:
Originally Posted by caseyjames View Post
How hard would it be to implement a function to read-in small midi files to use as sequences? Has anyone done anything like that with this yet?

This seems like a very useful template for future projects.
Can't read MIDI files into JS AFAIK.
Mich is offline   Reply With Quote
Old 03-24-2011, 01:43 PM   #39
caseyjames
Human being with feelings
 
caseyjames's Avatar
 
Join Date: Jul 2009
Posts: 632
Default

Not that I'd be able to write a handler for them, but is it not possible to write something within JS to read a midi file? Is it not able to load arbitrary files, even if it can't cope with them?
caseyjames is offline   Reply With Quote
Old 03-24-2011, 01:59 PM   #40
groundhum
Human being with feelings
 
groundhum's Avatar
 
Join Date: Jan 2011
Location: Finger Lakes, NY
Posts: 54
Default

Yep. Pages 5 & 6 of the js sdk. IXix's MIDI_Velocifier II will show you how.
__________________
Michael Rees, composer &c.
extraneous data
groundhum is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 11:02 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.