Professional Documents
Culture Documents
Rochebois
1 Notation
v() function
v(1.3) function evaluation
v[] table
v[7] access to a table (integers only)
Most short code examples are sort of jsfx (plugin language embedded in the Reaper DAW).
* I'm DR from the Universit Paris Sud Orsay (French Thesis about Multiple Wavetable Synthesis). I worked with the
Canam Computers company in the late 90s on many audio DSP algorithms including the Ongaku pitch tracker, the
EdgE anti aliasing method (very similar to "bleps") and variants of DPW. Contact:
Smashed.Transistors@gmail.com
1
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
Contents
1 Notation.............................................................................................................................................1
2 A simple Wavetable Oscillator...........................................................................................................3
2.1 Wave tables, How to read them.................................................................................................3
2.2 Digital Oscillators, Phases and Frequencies..............................................................................4
2.3 Digital Oscillators, Accessing the wavetable.............................................................................4
2.4 Digital Hell: Wavetable Aliasing...............................................................................................5
3 The popular cure: MIP MAPs............................................................................................................6
4 The old trick: Pre integration.............................................................................................................6
4.1 Memories...................................................................................................................................6
4.2 Back to the problem...................................................................................................................7
4.3 Oversampling.............................................................................................................................7
4.4 Area............................................................................................................................................7
4.5 Integral.......................................................................................................................................8
4.6 Pre integration............................................................................................................................8
4.7 A perfect world ?........................................................................................................................8
5 Cousins..............................................................................................................................................9
5.1 Differentiated Parabolic Waveforms..........................................................................................9
5.2 Differentiated Polynomial Waveforms......................................................................................9
6 Back to Pre integrated wavetables...................................................................................................10
6.1 Higher order pre integrated wavetables...................................................................................10
6.2 Anti Aliased Distortion............................................................................................................10
6.3 Anti Aliased Phase Modulation................................................................................................11
Appendix A Commented JSFX code examples..................................................................................12
Appendix A.1 An Anti Aliased Walsh Function synth...................................................................12
Appendix A.2 An Anti Aliased 3 Operator Parabola Phase Modulation synth..............................24
2
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
A wave table consists in a table that contains a set of samples that can be looped. The samples
represent a cycle of the signal. "Samples" means that values "v" of the analog signal have been
picked at regular time intervals.
A wavetable is very handy, it allows to access any value of the wave at any moment. For example,
let's say that we want to know what's the value of the wavetable at position 7, we can tell it directly
by accessing the table. v[7]
To get an idea of what happens between two samples we must interpolate. The straightforward
interpolation method is linear interpolation, we just connect the dots.
We guess that the value is somewhere between the values v[7] and v[8]. Linear interpolation
consists in mixing those two values:
3
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
p will be incremented at every tick of the main sampling rate (you know, 44.1kHz or 48KHz aka
srate in jsfx).
The rate at which it will be incremented "dp" controls the frequency of the oscillator.
Oups, i forgot that an oscillator has to cycle, here "p" goes away. We can fix a limit to "p" and tell
him to go back.
Let's say we want it to stay in the [0 16[ range, we can code:
p += dp;
p >= 16 ? p -= 16;
4
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
Everything sounds good so far, but what happens if we read our wavetable faster ?
We miss some details and get some others from the original waveform, depending on the position of
the phase accumulator for each iteration.
And even worse, we don't miss or get the same details at each cycle of the oscillator. The oscillator's
output is no more cyclic, there is some aperiodic dirt in the high end of the spectrum. Some say it is
metallic and cold, digital cold.
5
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
It's hard to remove details on the fly, so, the solution consists in preparing a set of wavetables (the
MIPMAP). Each wavetable of the MIPMAP set contains just the right amount of details for a
certain range of frequencies (i.e. MIDI notes).
The main drawback is that it uses lots of memory, but today, memory is cheap. So...
This is the method i use for the PWT_synth. It is a very efficient method and the most popular
antialiasing method for wavetables so far.
4.1 Memories
Pre integration is a trick I used in the 90s. The computers I used for my thesis work were not very
powerful and had much less memory than an average laptop of 2016. As my work implied multiple
wavetables, I had to find a cure to my wavetable aliasing problem. Pre integration was the method I
opted for.
Ideally, we should read what is in a certain region of the wavetable, the "what happened since last
time region".
4.3 Oversampling
Sure, but how can we do that ? How can we know what was in the "what happened since last time
region".
Well, we can cheat and take more samples - that's called oversampling - but it will cost some CPU
6
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
4.4 Area
How can we get the mean value of the "what happened since last time region" instead of a ponctual
value taken in this region ?
Has far as i learnt in high school, the mean value is the area under the curve devided by the width of
the region.
4.5 Integral
An area under a function can be exactly calculated by substracting its integrand at the limit points.
So, we need this nice and handy Iv() function. Sorry, we have to do some maths.
The v() function - the linearly interpolated samples - is made of line segments.
7
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
Pre integration allows to calculate the mean value of our wavetable on any region by substracting to
values and dividing the result by the width of the region.
It is far superior to the trivial linear interpolation scheme.
Sure, it gives the mean value of the "what happened since last time region", but that mean value is
far from a perfect anti aliasing filter.
A mean value provides what's called a "box filter". The box filter frequency response is a sinc
function (DSP Related:Running Sum Lowpass Filter). Let's say that it cannot rivalise with Fourier
band limiting used in MIPMAPs.
That's why Pre integration should not be seen as a perfect antialiasing scheme. To be efficient it can
be used with other methods such as oversampling. In this case, it can be as effective as MIPMAPing
and more importantly it can be extended beyond wavetables.
5 Cousins
Pre integration of wavetables have some cousins: the DPWs, Differenciated Parabolic Waveforms.
A DPW oscillator does not use wave tables. It generates a parabolic waveform based on its phase.
This parabola is differentiated to generate a somewhat band limited sawtooth. Other waveforms,
such as square and rectangle are generated by combining sawteeth.
8
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
In the late 90s a group of my students worked at a final year project. This final year project
consisted in coding a simple band limited oscillator without wavetables. The generator should
exclusively be based on math functions.
I suggested the idea of Parabolic Waveforms and that they may experiment with higher order
Polynomials and higher order differentiators.
The results were quite good: low aliasing and low CPU. The "box filter" implied by the first order
differentiator was replaced by something like "the box filter of a box filter of a box filter"... which
starts to look like a gaussian filter.
The main drawback of the method is that it needs high accuracy. Floats were enough for a third
order integration/differentiation scheme. Higher order schemes needed doubles.
See, for example, this old thread on the KVR DSP forum:
http://www.kvraudio.com/forum/viewtopic.php?p=1710116#1710116
Later i used this method in a VSTi synth you may find somewhere on the net "The Vagabond King".
The Vagabond King VSTi
The case of wavetables is more sensitive to accuracy than the generation of sawteeth by the DPW
method. Instead of a polynomial, you have to deal with a set of polynomial segments and ensure
that calc errors are kept low.
Nevertheless, today, performing maths with 64bit doubles is not a big deal. So I can use a second or
third order integration/differentiation scheme on wavetables without problems.
I use this method in the TiaR_Ze_Cheesy_Synth.jsfx, it is a jsfx plugin for Reaper (available from
the Reapack/ReaTeam repository).
The main advantage VS mipmaping is that the wavetable pre integration process is much simpler
than MIPMAPing.
9
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
Pre integrated tables can be used beyond wavetables. In areas not available to MIPMAPing.
Many years ago i tried to use dynamic MIPMAPing i.e. switching or crossfading between
MIPMAP levels depending on the slew rate of the input... The crossfading induced aliasing by
itself.
So let's see how we can use pre integrated tables to get an anti aliased distortion.
It's much like a wavetable, but instead of accessing the table with a phase we will access it with the
input signal. We have to go from a serial access to a random access.
I tried it, it works. But as the input signal is much much less predictable than a phase, calc accuracy
is quite delicate. Anyway, I achieved nice results using a second order scheme even with
complicated distortion. (for example: Pre integrated Dissymetric smooth "tube" saturation).
Second Order pre integration + oversampling by two may be an effective and elegant solution to the
problem of aliasing in non linearities.
Distortion is a special case of phase modulation. It is the case were the frequency of the oscillator is
zero.
So, based on my experiments with anti aliasing distortion, i went a step further.
Generaly speaking, phase modulation (or frequency modulation) implies sine oscillators. Phase
modulation of complex wavetable oscillators often generate too many harmonics and induce strong
aliasing. A second orderpre integration allows to implement such a modulation. See Anti aliased 3
operator PM synth. for an operational implementation (detailed information in the appendix).
10
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
slider1:1<-1,1,0.001>Seq. 1
slider2:0<-1,1,0.001>Seq. 2
slider3:0<-1,1,0.001>Seq. 3
slider4:0<-1,1,0.001>Seq. 4
slider5:0<-1,1,0.001>Seq. 5
slider6:0<-1,1,0.001>Seq. 6
slider7:0<-1,1,0.001>Seq. 7
slider8:0<-1,1,0.001>Seq. 8
slider9:0<-1,1,0.001>Seq. 9
slider10:0<-1,1,0.001>Seq. 10
slider11:0<-1,1,0.001>Seq. 11
slider12:0<-1,1,0.001>Seq. 12
slider13:0<-1,1,0.001>Seq. 13
slider14:0<-1,1,0.001>Seq. 14
slider15:0<-1,1,0.001>Seq. 15
slider16:0<-1,1,0.001>Seq. 16
slider20:0<-24,24,0.0001>Detune (semitones)
slider21:5.5<1,20>Vibrato rate
slider22:0.4<0,1,0.0001>Vibrato depth
slider23:1<0,1,0.0001>Glide rate
slider24:0<0,1>Tremolo depth
slider31:-3<-3,1,0.0001>Attack
slider32:0<-3,1,0.0001>Decay
slider33:1<0,1,0.0001>Sustain
slider34:-2<-3,1,0.0001>Release
11
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
slider41:0.5<0,1,0.0001>Pan
slider42:0<-24,12>Gain (dB)
// ___________________________________________________________________
@init
//_____________________________________________________________________
function ADSR_setP10(A D S R) (
// decay coefs ref time(s)
this.A = 1 - exp(log(0.33) / ((10^A) * (srate/KRATE)));
this.A = min(this.A, 1);
this.D = 1 - exp(log(0.10) / ((10^D) * (srate/KRATE)));
this.D = min(this.D, 1);
this.S = S;
this.R = 1 - exp(log(0.10) / ((10^R) * (srate/KRATE)));
this.R = min(this.R, 1);
);
//_____________________________________________________________________
// Control rate processing
function ADSR_kProc(gate trig)
local() (
gate === 0 ? this.AttackSeg = 0;
trig ? this.AttackSeg = 1;
this.ASR < gate ?
this.ASR += this.A * (gate - this.ASR)
: this.ASR += this.R * (gate + 0.00000001 - this.ASR);
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
this.AttackSeg === 1 ? (
this.ADSR += this.A * (1.5 - this.ADSR);
this.ADSR >= 1 ?
( this.ADSR = 1.0;
this.AttackSeg = 0;
);
);
this.AttackSeg === 0 ? (
gate != 0 ? (
this.ADSR += this.D * (this.S - this.ADSR);
) : (
this.ADSR += this.R * (0.00000001 - this.ADSR);
);
);
this.ASR * this.ADSR;
);
// ____________________________________________________________________
function WSH_init()
instance(sal) local(seq x x0 x1 x2 x3 x4)(
sal = ad; ad += 32*16;
x = 0; loop(32,
x0 = (x& 1) ?1:-1;
x1 = ((x& 2)>>1)?1:-1;
x2 = ((x& 4)>>2)?1:-1;
x3 = ((x& 8)>>3)?1:-1;
x4 = ((x&16)>>4)?1:-1;
sal[x + 32 * 0] = x4;
sal[x + 32 * 1] = x3 ;
sal[x + 32 * 2] = x2*x3*x4;
sal[x + 32 * 3] = x2 ;
sal[x + 32 * 4] = x1*x2* x4;
sal[x + 32 * 5] = x1*x2*x3 ;
sal[x + 32 * 6] = x1 *x3*x4;
sal[x + 32 * 7] = x1 ;
12
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
13
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
14
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
);
)
: status == $xB0 ? (
msg2 == 1 ? modWhl = msg3 * (1/127);
);
co_vib = vibMin + modWhl * (vibMax - vibMin);
co_vibA = max(co_vib, vibMin + aftertouch * (vibMax - vibMin));
col(0.0,0,0.5,1);
gfx_rect(0,0,32*12+16, 64);
col(1.0,1.0,1.0,1);
15
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
gi = 0; loop(32,
gx = gi*12+8;
gi>0?
gfx_line(gx, 32-144*piwt.x[gi-1], gx, 32-144*piwt.x[gi])
:
gfx_line(gx, 32, gx, 32-144*piwt.x[gi]);
gfx_line(gx, 32-144*piwt.x[gi], gx+12, 32-144*piwt.x[gi]);
gi+=1;
);
gx = gi*12+8;
gfx_line(gx, 32-144*piwt.x[31], gx, 32);
slider1:1<-1,1,0.001>Seq. 1
slider2:0<-1,1,0.001>Seq. 2
slider3:0<-1,1,0.001>Seq. 3
slider4:0<-1,1,0.001>Seq. 4
slider5:0<-1,1,0.001>Seq. 5
slider6:0<-1,1,0.001>Seq. 6
slider7:0<-1,1,0.001>Seq. 7
slider8:0<-1,1,0.001>Seq. 8
slider9:0<-1,1,0.001>Seq. 9
slider10:0<-1,1,0.001>Seq. 10
slider11:0<-1,1,0.001>Seq. 11
slider12:0<-1,1,0.001>Seq. 12
slider13:0<-1,1,0.001>Seq. 13
slider14:0<-1,1,0.001>Seq. 14
slider15:0<-1,1,0.001>Seq. 15
slider16:0<-1,1,0.001>Seq. 16
[...]
slider41:0.5<0,1,0.0001>Pan
slider42:0<-24,12>Gain (dB)
This is the header section which defines the sliders: Digital "Harmonics" amplitudes, Detune and
Vibrato, Enveloppe and Pan controls are defined here.
// ___________________________________________________________________
@init
//_____________________________________________________________________
[...]
The @init section contains function declarations and init code such as the ADSR enveloppe code.
We won't focus on that, we'd rather look for what's specific to this synth.
16
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
// ____________________________________________________________________
function WSH_init()
instance(sal) local(seq x x0 x1 x2 x3 x4)(
sal = ad; ad += 32*16;
x = 0; loop(32,
x0 = (x& 1) ?1:-1;
x1 = ((x& 2)>>1)?1:-1;
x2 = ((x& 4)>>2)?1:-1;
x3 = ((x& 8)>>3)?1:-1;
x4 = ((x&16)>>4)?1:-1;
sal[x + 32 * 0] = x4;
sal[x + 32 * 1] = x3 ;
sal[x + 32 * 2] = x2*x3*x4;
sal[x + 32 * 3] = x2 ;
sal[x + 32 * 4] = x1*x2* x4;
sal[x + 32 * 5] = x1*x2*x3 ;
sal[x + 32 * 6] = x1 *x3*x4;
sal[x + 32 * 7] = x1 ;
sal[x + 32 * 8] = x0*x1 *x4;
sal[x + 32 * 9] = x0*x1 *x3 ;
sal[x + 32 * 10] = x0*x1*x2*x3*x4;
sal[x + 32 * 11] = x0*x1*x2 ;
sal[x + 32 * 12] = x0 *x2 *x4;
sal[x + 32 * 13] = x0 *x2*x3 ;
sal[x + 32 * 14] = x0 *x3*x4;
sal[x + 32 * 15] = x0 ;
x += 1;
);
);
That initialises the 16 walsh function tables. These tables will be added together in the Pre
Integrated Wave Table everytime a slider is moved.
17
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
This is a utility function that removes the mean value (DC) of a table. It is an important feature
when you integrate periodic tables more than once: it guaranties that the integrals will be periodic
too.
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
function PIWT_norm(t n) local(i m)(
m = 0; i = 0; loop(n, m = max(t[i], abs(m)); i += 1; );
m = 1.1/(m+0.1); i = 0; loop(n, t[i] *= m; i += 1; );
);
This is another utility function that roughly normalize the content of a table.
The input table is x. It consists of 32 steps, a mixture of Walsh functions, no linear interpolation.
First of all, we remove its mean value by calling the utility function PIWT_dcRemove(x, n);
18
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
"p0" being the integer part of p and "a" being its float part.
Accumulating z values ensures that the polynomial segments "connect the dots".
At the boundaries, we have
I 2 ( p0 + )I 2 ( p 0)
i.e.
1 1
z [ p0 ]+ y [ p 0]+ 2 x [ p0 ]z [ p01]+(1) y [ p 01]+ (1)2 x [ p01]
2 2
The limit of this equation gives us our integration code.
The last step calculates w, the third order integral. It is mostly similar to the previous step, so i won't
go into it.
19
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
Nota: I scale x and y with 1/2 and 1/6 factors. It is an optimisation that will be used in
PIWT_aProc()
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
function PIWT_aProc()
instance(n dp _dp _dp3 p out w z y x w0 w1 w2 w3) local(p0 a)(
p += dp; p -= n*(p>=n); p0 = p|0; a = p-p0;
w3 = w[p0] + a * (z[p0] + a * (y[p0] + a * x[p0]));
out = (w3 - w0 + 3 * (w1 - w2)) * _dp3;
w0 = w1; w1 = w2; w2 = w3;
out;
);
p += dp; p -= n*(p>=n);
This is the phase increment and modulo (we stay in [0, n[ )
p0 = p|0; a = p-p0;
Splits p into its integer and float parts
20
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
// Call before aProc if there is a discontinuity of dp _dp
function PIWT_disc()
instance(n dp _dp _dp3 p out w z y x w0 w1 w2) local(p0 a)(
p -= 2*dp; p += n * (p < 0); p -= n * (p >= n); p0 = p|0; a = p - p0;
w0 = w[p0] + a * (z[p0] + a * (y[p0] + a * x[p0]));
[...]
// ___________________________________________________________________
@slider
[...]
x = 0; loop(32,
piwt.x[x] = slider1 * wsh.sal[x + 32 * 0];
seq = 1;loop(15,
piwt.x[x] += slider(seq + 1) * wsh.sal[x + 32 * seq];
seq += 1;
);
x += 1;
);
PIWT_dcRemove(piwt.x,32);
PIWT_norm(piwt.x,32);
piwt.PIWT_integ();
piwt.PIWT_disc();
The @slider section takes in charge the update of the Pre Integrated
Wave Table.
21
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
x += 1;
);
// ___________________________________________________________________
@block
while (midirecv(offset, msg1, msg23)) (
[...]
);
// ___________________________________________________________________
@sample
k2 <= 0 ? (
k2 = KRATE;
dEnv = (adsr.ADSR_kProc(gate, trig) - env)*_KRATE;
trig = 0;
);
k2 -= 1;
This is a "control rate" sub-section. This code is run every KRATE sample. This kind of
optimisation saves lots of CPU without compromising sound quality if you choose KRATE
correctly.
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
k <= 0 ? (
k = 32;
bendFactorF += 0.05 * (bendFactor - bendFactorF);
slider23 >=1 ? dpg = bendFactorF * dpc
: dpg += glideCoef * (bendFactorF * dpc - dpg);
pLfo += dpLfo; pLfo -= 2*$pi*(pLfo>0);
lfo = sin(pLfo);
dTrem = (1/32)*( 1 + slider24 * (0.5*lfo+0.5 - 1) - trem);
co_vibAf += 0.05 * (co_vibA - co_vibAf);
piwt.dp = dpg * (1 + co_vibAf * lfo);
piwt._dp = 1 / piwt.dp;
piwt._dp3 = piwt._dp * piwt._dp * piwt._dp;
22
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
piwt.PIWT_disc();
);
k -= 1;
This other sub-section is run every 32 samples, it contains updates of the various MIDI controls that
affect the pitch of the sound. Here piwt.dp, piwt._dp and piwt._dp3 are updated and
piwt.PIWT_disc() is called to avoid glitches.
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
env += dEnv;
trem += dTrem;
out = min(10,max(-10,trem*env*piwt.PIWT_aProc()));
spl0 += gLeft * out;
spl1 += gRight * out;
This code is run for every sample. The enveloppe and tremolo are linearly interpolated.
env += dEnv;
trem += dTrem;
slider6:sl_R1=0.9997<0,3,0.000001>Ratio 1
slider7:sl_I1=0.5<0,3,0.0001>I1
slider9:sl_R0=1<0,3,0.000001>Ratio 0
slider11:sl_A=-3<-3,0,0.001>Attack
slider12:sl_R=0<-2,1,0.001>Release
// ___________________________________________________________________
23
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
@init
function I0(x)( x+=16;x-=x|0; (6*x-6)*x+1 );
function I1(x)( x+=16;x-=x|0; ((2*x-3)*x+1)*x );
function I2(x)( x+=16;x-=x|0; ((0.5*x-1)*x+0.5)*x*x );
function AA2_para(dp m)
instance(p x0 x1 y0 y1 I2_0 I2_1 I1_0 I1_1 out)(
p += dp; p >= 1 ? (p -= 1; x0 -= 1; y0 -= 1; );
x1 = x0; I2_1 = I2_0; x0 = p + m;
I2_0 = I2(x0);
y1 = y0; I1_1 = I1_0; y0 = 0.5 * (x0 + x1);
I1_0 = x0 == x1 ? I1(y0) : (I2_0 - I2_1)/(x0 - x1);
out = y0 == y1 ? I0(y0) : (I1_0 - I1_1)/(y0 - y1);
);
// ___________________________________________________________________
@block
while (midirecv(offset, msg1, msg23)) (
msg2 = msg23 & 0x7F; msg3 = msg23 >> 8; status = msg1 & $xF0;
status == $x80 ? ( status = $x90; msg3 = 0; ); // note off
status == $x90 ? ( // note on
msg3 == 0 ? (msg2 == note ? gate = 0;)
: ( gate = sqrt(msg3 * (1/127)); note = msg2;
dp = (440/srate)*2^((note-69)*(1/12));
);
);
midisend(offset, msg1, msg23);
);
dp0 = sl_R0 * dp; dp1 = sl_R1 * dp; dp2 = sl_R2 * dp;
A = 1/(srate*10^sl_A);
R = 1/(srate*10^sl_R);
// ___________________________________________________________________
@sample
env += env>gate ? R*(gate-env) : A*(gate-env);
y2 = osc2.AA2_para(dp2,0);
y1 = osc1.AA2_para(dp1, env*sl_I2 * y2);
y0 = osc0.AA2_para(dp0, (0.5+0.5*env)*sl_I1 * y1);
spl0 = spl1 = min(3,max(-3,env*y0));
That's clearly why I like JSFX: you can test your ideas with very few code lines.
The first section describes the sliders that will control the synth.
The second section is the @init section, it contains stuff the plugin will do at init. It's also the place
where you can declare your functions.
The @block section is usually the place where you process MIDI information.
The @sample section is called for every sample. The stereo output is spl0 and spl1.
In this example, the specific code is in the functions and in the @sample section (colored red). I will
comment those.
24
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
The function
function AA2_para(dp m)
instance(p x0 x1 y0 y1 I2_0 I2_1 I1_0 I1_1 out)(
p += dp; p >= 1 ? (p -= 1; x0 -= 1; y0 -= 1; );
x1 = x0; I2_1 = I2_0; x0 = p + m;
I2_0 = I2(x0);
y1 = y0; I1_1 = I1_0; y0 = 0.5 * (x0 + x1);
I1_0 = x0 == x1 ? I1(y0) : (I2_0 - I2_1)/(x0 - x1);
out = y0 == y1 ? I0(y0) : (I1_0 - I1_1)/(y0 - y1);
);
25
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
So...
function AA2_para(dp m)
instance(p x0 x1 y0 y1 I2_0 I2_1 I1_0 I1_1 out)(
p += dp; p >= 1 ? (p -= 1; x0 -= 1; y0 -= 1; );
x1 = x0; I2_1 = I2_0; x0 = p + m;
I2_0 = I2(x0);
y1 = y0; I1_1 = I1_0; y0 = 0.5 * (x0 + x1);
I1_0 = x0 == x1 ? I1(y0) : (I2_0 - I2_1)/(x0 - x1);
out = y0 == y1 ? I0(y0) : (I1_0 - I1_1)/(y0 - y1);
);
26
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
The new value for I2_0 is the second order integral of our parabola at x0.
y1 = y0; I1_1 = I1_0; y0 = 0.5 * (x0 + x1);
Same thing as before with the first order integral of our parabola.
The problem is when x0 is near x1, dividing by zero is a bad option, that's why we have a plan B:
directly calculate I1(y0).
Note that the functions I0 I1 and I2 are consistent (no DC offset, no scale factors, etc). This is
important in order to avoid glitches.
27
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
The Saturation function is a smooth dissymetric function defined in a table. The function is defined
by linear segments. The algorithm is much similar to the one used int Ze Cheezy Harmonic Synth
(even if its wavetable is defined by steps).
function PIT_init(n)(
this.n = n; this.n16 = n * 16;
this.dv = ad; ad += n; this.v = ad; ad += n;
this.Iv = ad; ad += n; this.IIv = ad; ad += n;
);
// _____________________________________________________________________
// (performs the pre integrations)
function PIT_update()
instance(n dv v Iv IIv) local(p dcOffset)(
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
// Remove DC offset from the input table
dcOffset = v[0]; p = 1; loop(n - 1, dcOffset += v[p]; p += 1; );
dcOffset /= n; p = 0; loop(n, v[p] -= dcOffset; p += 1; );
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
// calc the slope between two values (for linear interpolation)
p = 0; loop(n, dv[p] = v[(p+1) % n] - v[p]; p += 1; );
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
// First polynomial integration Iv
dcOffset = Iv[0] = 0; 0; p = 0;
loop(n - 1,
Iv[p+1] = Iv[p] + v[p] + 0.5 * dv[p];
dcOffset += Iv[p+1]; p+=1; );
dcOffset /= n; p = 0; loop(n, Iv[p] -= dcOffset; p += 1;);
28
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
// _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
// Second polynomial integration IIv
dcOffset = IIv[0] = 0;
p = 0; loop(n - 1,
IIv[p+1] = IIv[p] + Iv[p] + 0.5 * v[p]+(1/6)*dv[p];
dcOffset += IIv[p+1]; p+=1; );
dcOffset /= n;p = 0;loop(n, IIv[p] -= dcOffset;p+=1;);
);
function PIT_I0(p)
instance(n n16 dv v) local(p0 a)(
a = p - (p0 = p|0); // "a" is the float part, "p0" is the integer part
p0 %= n; // modulo
dv[p0] * a + v[p0];
);
returns the I0 i.e. the raw function (with linear interpolation)
function PIT_I1(p)
instance(n n16 dv v Iv) local(p0 a)(
a = p - (p0 = p|0);
p0 %= n;
( dv[p0] * 0.5*a + v[p0]) * a + Iv[p0];
);
returns the first integration value I1(p)
function PIT_I2(p)
instance(n n16 dv v Iv IIv) local(p0 a)(
a = p - (p0 = p|0);
p0 %= n;
(( dv[p0] * (1/3)*a + v[p0]) * 0.5*a + Iv[p0]) * a + IIv[p0];
);
returns the second integration value I2(p)
29
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
The main difference with the parabola PM synth is that the I0, I1 and I2 functions use tables instead
of an integrated parabolic function.
function PIO_update2()
instance(p x1 x2 I2_1 I2_2 y0 y1 I1_0 I1_1 out)(
I2_2 = this.PIT_I2(x2);
I1_1 = x1 == x2 ? this.PIT_I1(y1) : (I2_2 - this.PIT_I2(x1)) / (x2 - x1);
);
// _____________________________________________________________________
pit.PIT_init(256);
i=0;loop(pit.n,
x = i < pit.n / 2 ? i : i - pit.n;
x *= 0.2;
x > 0 ? pit.v[i] = x / ((1+abs(x)^2.5)^(1/2.5))
: pit.v[i] = x / ((1+abs(x)^4.0)^(1/4.0));
i+=1;
);
This is the initialisaton code. It initializes the table with the dissymetric and smooth saturation.
pit.PIT_update();
pio.PIO_link(pit);
pio.PIO_update2();
30
AA of Oscillators and Distortions with Pre Integrated WaveTables T.Rochebois
Notes:
+ pit.n16 avoids negative indexing of the table
-0.03717 offsets the input so that a zero input implies a zero output
Both interpolator outputs interp.y0 and interp.y1 are sent to the core processing
function pio.PIO_aProc2.
The results are decimated with dec2_96_59.
31