|
|
|
07-22-2009, 09:06 AM
|
#1
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
07-22-2009, 09:07 AM
|
#2
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
07-22-2009, 12:45 PM
|
#3
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
fast integer conversations
fast fractional to integer conversation for negative or positive input (x):
floor:
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.
|
|
|
07-22-2009, 01:23 PM
|
#5
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
07-22-2009, 02:28 PM
|
#6
|
Human being with feelings
Join Date: Sep 2007
Location: trondheim, norway
Posts: 375
|
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.
|
|
|
07-22-2009, 03:06 PM
|
#7
|
Human being with feelings
Join Date: May 2009
Posts: 1,265
|
Quote:
Originally Posted by cern.th.skei
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! :(
|
|
|
07-22-2009, 10:07 PM
|
#8
|
Human being with feelings
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
|
great thread, thanks!
|
|
|
07-23-2009, 02:24 AM
|
#9
|
Human being with feelings
Join Date: Sep 2007
Location: trondheim, norway
Posts: 375
|
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
|
|
|
07-23-2009, 02:50 AM
|
#10
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
Yes, great tips here!
So here are some (more basic :-) things I have noted. HTH... Here I focus on:
Quote:
Originally Posted by liteon
- 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:
[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 !
|
|
|
07-24-2009, 05:44 AM
|
#11
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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
|
|
|
07-24-2009, 06:08 AM
|
#12
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
fast pow() from identity
using the above approximation of exp() and the identity:
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
|
|
|
07-24-2009, 07:40 AM
|
#13
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
07-24-2009, 09:48 AM
|
#14
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
07-24-2009, 12:55 PM
|
#15
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
07-28-2009, 04:48 AM
|
#16
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
08-20-2009, 07:12 AM
|
#17
|
Human being with feelings
Join Date: Sep 2008
Location: Sweden
Posts: 7,432
|
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...
|
|
|
08-20-2009, 08:16 AM
|
#18
|
Administrator
Join Date: Jan 2005
Location: NYC
Posts: 15,746
|
Quote:
Originally Posted by Jeffos
=> 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.
|
|
|
08-27-2009, 08:34 AM
|
#19
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,891
|
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
|
|
|
08-28-2009, 09:35 AM
|
#20
|
Mortal
Join Date: Dec 2008
Location: France
Posts: 1,969
|
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
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.
|
|
|
08-28-2009, 05:22 PM
|
#21
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,653
|
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.
|
|
|
09-01-2009, 06:30 PM
|
#22
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
09-03-2009, 03:43 PM
|
#23
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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
|
|
|
09-03-2009, 05:13 PM
|
#24
|
Human being with feelings
Join Date: Aug 2008
Posts: 1,144
|
Quote:
Originally Posted by Justin
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;
|
Last edited by whatsup; 09-03-2009 at 05:15 PM.
|
|
|
09-09-2009, 12:25 PM
|
#25
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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
|
|
|
12-12-2009, 03:01 AM
|
#27
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
using memory slots
|
|
|
02-11-2010, 07:49 PM
|
#28
|
Human being with feelings
Join Date: Apr 2008
Posts: 510
|
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.
|
|
|
02-11-2010, 08:59 PM
|
#29
|
Human being with feelings
Join Date: Mar 2008
Location: Sydney, Australia
Posts: 3,955
|
thanks again, liteon. these posts are really useful. still love your JS plugins as well, the moog filter and waveshaper end up on everything!
|
|
|
06-12-2010, 09:38 AM
|
#31
|
Human being with feelings
Join Date: Jan 2010
Location: Bergen, Norway.
Posts: 78
|
Quote:
Originally Posted by zorn
|
Awesome, but how do i add the custom language to notepad++ ?
|
|
|
06-12-2010, 03:49 PM
|
#33
|
Human being with feelings
Join Date: Jan 2010
Location: Bergen, Norway.
Posts: 78
|
Quote:
Originally Posted by Fabian
You could start by googeling.
|
Yeah, I tried to do that, and did that. does not work for me apparently.
|
|
|
07-05-2010, 06:10 AM
|
#34
|
Human being with feelings
Join Date: Jan 2007
Location: mcr:uk
Posts: 3,891
|
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.
|
|
|
02-26-2011, 08:31 AM
|
#36
|
Human being with feelings
Join Date: Jan 2011
Location: Finger Lakes, NY
Posts: 54
|
a bit of noob advice
Quote:
Originally Posted by IXix
|
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.
Last edited by groundhum; 02-26-2011 at 08:47 AM.
Reason: bad manners
|
|
|
03-23-2011, 01:17 PM
|
#37
|
Human being with feelings
Join Date: Jul 2009
Posts: 633
|
Quote:
Originally Posted by IXix
|
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.
|
|
|
03-24-2011, 08:33 AM
|
#38
|
Human being with feelings
Join Date: May 2009
Posts: 1,265
|
Quote:
Originally Posted by caseyjames
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.
|
|
|
03-24-2011, 01:43 PM
|
#39
|
Human being with feelings
Join Date: Jul 2009
Posts: 633
|
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?
|
|
|
03-24-2011, 01:59 PM
|
#40
|
Human being with feelings
Join Date: Jan 2011
Location: Finger Lakes, NY
Posts: 54
|
Yep. Pages 5 & 6 of the js sdk. IXix's MIDI_Velocifier II will show you how.
|
|
|
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:19 AM.
|