Cabbage Logo
Back to Cabbage Site

How to "morph" sounds

Hello all,

I need a little help about the following puzzle :
I have 3 oscilators driven by midi keyboard.
There fore i get 3 audio signals : a1,a2, a3
I want to morph overtime the overall output aOut from a1 -> a2 ->a3.

Considering 2 aSignals it is easy to achieve , I can even make a plateau when using linesegr (i.e. a1 for one sec, then ramp to a2 then plateau on a2 until midi note is released. :

kMorph linsegr 0, 2, 1    ;  this provides a ramp of 2 seconds to reach a2 starting from a1 at time 0.
aOut = (1-kMorph) *a1  + kMorph * a2 

But how to do it with 3 oscillators ? taking into account that a3 cannot start before a1 and a2 have passed…
I wish not to use table at this stage…
thanks for your help

Controlling it by using math operations I would probably do something like this:

kMorph linsegr -1, 2, 0, 2, 1    
kOne = (kMorph < 0 ? abs(kMorph) : 0)
kTwo = 1-abs(kMorph)
kThree = 1-(kOne+kTwo)
aOut = kOne*a1  + kTwo*a2 + kThree*a3

There’s probably a much easier way, but I can’t think of it right now.

Well, here’s a slightly longer but more intuitive way of doing it. I know you mentioned no tables, but it’s quite minimal usage of them, should be easy to understand based on the comments:

;Tables to hold amplitude envelope for each of the 3 oscils. Gen07 is used, Gen05 can be used if you want exponential curves, but make sure to use non-zero values
giAmp1		ftgen		1, 0, 1024, 7, 1, 1024, 0      
giAmp2		ftgen		2, 0, 1024, 7, 0, 512, 1, 512, 0     
giAmp3		ftgen		3, 0, 1024, 7, 0, 1024, 1

aSig1		poscil		0.1, 200
aSig2 		poscil		0.1, 500
aSig3		poscil		0.1, 900


iDuration         =     5
;A pointer variable to sweep through the table
aPointer	        linseg		0, iDuration, 1

;Amplitude envelopes generated by looking up the respective tables, mode 1 is used to limit table indexing between 0 and 1. This is good if you plan to change the size of your tables
aAmpEnv1	tablei		aPointer, giAmp1, 1
aAmpEnv2	tablei		aPointer, giAmp2, 1
aAmpEnv3 	tablei		aPointer, giAmp3, 1

aMix	=		aSig1 * aAmpEnv1 + aSig2 * aAmpEnv2 + aSig3 * aAmpEnv3

Also, there is an opcode for morphing (named “ftmorf”.)
http://www.csounds.com/manual/html/ftmorf.html

Thank you a lot guys ! You were really helpful.

Here is the solution I took … maybe not very beautiful but it works. Using this I can set a "not blended " sound for each oscil. I will take a closer look at GEN07 table too.
What is your opinion on this ?

`    <Cabbage>
    form caption("Untitled") size(400, 300), colour(58, 110, 182), pluginID("def1")
    groupbox bounds(0, 0, 250, 160) colour(228, 238, 98, 255) text("Osc sound merging"){ 
checkbox bounds(5, 4, 12, 12) channel("Osc_Merge_On") colour:0(99, 96, 96, 255) colour:1(80, 94, 229, 255) fontcolour:0(36, 32, 32, 255) shape("circle") 

checkbox bounds(50, 65, 20, 20) channel("SelectOsc1_1")  colour:1(160, 89, 241, 255) radiogroup(750) 
checkbox bounds(50, 86, 20, 20) channel("SelectOsc1_2")  colour:1(160, 89, 241, 255) radiogroup(750) 
checkbox bounds(50, 107, 20, 20) channel("SelectOsc1_3") colour:1(160, 89, 241, 255) radiogroup(750) 
checkbox bounds(71, 65, 20, 20) channel("SelectOsc2_1") colour:1(160, 89, 241, 255) radiogroup(751) 
checkbox bounds(71, 86, 20, 20) channel("SelectOsc2_2")  colour:1(160, 89, 241, 255) radiogroup(751) 
checkbox bounds(71, 107, 20, 20) channel("SelectOsc2_3")  colour:1(160, 89, 241, 255) radiogroup(751) 
checkbox bounds(92, 65, 20, 20) channel("SelectOsc3_1") colour:1(160, 89, 241, 255) radiogroup(752) 
checkbox bounds(92, 86, 20, 20) channel("SelectOsc3_2")  colour:1(160, 89, 241, 255) radiogroup(752) 
checkbox bounds(92, 107, 20, 20) channel("SelectOsc3_3")  colour:1(160, 89, 241, 255) radiogroup(752) 

label bounds(5, 65, 40, 14) text("Osc A") fontcolour:0(0, 0, 0, 255) align("left") 
label bounds(5, 88, 40, 14) text("Osc B") fontcolour:0(0, 0, 0, 255) align("left") 
label bounds(5, 109, 40, 14) text("Osc C") fontcolour:0(0, 0, 0, 255) align("left") 
label bounds(50, 60, 40, 14) text("Seq 1") fontcolour:0(0, 0, 0, 255) rotate(-1.3, 0, 0) align("left") 
label bounds(71, 60, 40, 14) text("Seq 2") fontcolour:0(0, 0, 0, 255) rotate(-1.3, 0, 0) align("left") 
label bounds(92, 60, 40, 14) text("Seq 3") fontcolour:0(0, 0, 0, 255) rotate(-1.3, 0, 0) align("left") 
}


rslider bounds(120, 25, 40, 70) channel("Duration_segA") range(0, 3, 1, 1, 0.01) valuetextbox(0) 
rslider bounds(160, 25, 40, 70) channel("Duration_segB") range(0, 3, 1, 1, 0.01) valuetextbox(0) 
rslider bounds(200, 25, 40, 70) channel("Duration_segC") range(0, 3, 1, 1, 0.01) valuetextbox(0) 

checkbox bounds(140, 80, 80, 20) channel("SelectOsc_Loop") text("Loop") colour:1(160, 89, 241, 255) fontcolour:0(0, 0, 0, 255) fontcolour:1(0, 0, 0, 255) radiogroup(753) shape("circle") 
checkbox bounds(140, 105, 80, 20) channel("SelectOsc_LFO") text("LFO") colour:1(160, 89, 241, 255) fontcolour:0(0, 0, 0, 255) fontcolour:1(0, 0, 0, 255) radiogroup(753) shape("circle") 
;checkbox bounds(120, 122, 20, 20) channel("SelectOsc3_2") text("A->B") colour:1(160, 89, 241, 255) radiogroup(753) shape("circle")
;checkbox bounds(120, 144, 20, 20) channel("SelectOsc3_3")  colour:1(160, 89, 241, 255) radiogroup(753) shape("circle")
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
sr = 44100
ksmps = 32
nchnls = 2
0dbfs = 1
massign 0,2

instr 1  
if changed:k(chnget:k("SelectOsc1_1"),chnget:k("SelectOsc1_2"),chnget:k("SelectOsc1_3")) ==1 then
    if chnget:k("SelectOsc1_1") ==1 then 
        gkVoice1 =1
    endif
        if chnget:k("SelectOsc1_2") ==1 then 
        gkVoice1 =2
    endif
        if chnget:k("SelectOsc1_3") ==1 then 
        gkVoice1 =3
    endif
endif


if changed:k(chnget:k("SelectOsc2_1"),chnget:k("SelectOsc2_2"),chnget:k("SelectOsc2_3")) ==1 then
    if chnget:k("SelectOsc2_1") ==1 then 
        gkVoice2 =1
    endif
        if chnget:k("SelectOsc2_2") ==1 then 
        gkVoice2 =2
    endif
        if chnget:k("SelectOsc2_3") ==1 then 
        gkVoice2 =3
    endif
endif
if changed:k(chnget:k("SelectOsc2_1"),chnget:k("SelectOsc2_2"),chnget:k("SelectOsc2_3")) ==1 then
    if chnget:k("SelectOsc3_1") ==1 then 
        gkVoice3 =1
    endif
        if chnget:k("SelectOsc3_2") ==1 then 
        gkVoice3 =2
    endif
        if chnget:k("SelectOsc3_3") ==1 then 
        gkVoice3 =3
    endif
endif

if changed:k(chnget:k("Duration_segA"))==1 then 
    gkDuration_segA chnget "Duration_segA"
endif


if changed:k(chnget:k("Duration_segB"))==1 then 
    gkDuration_segB chnget "Duration_segB"
endif

if changed:k(chnget:k("Duration_segC"))==1 then 
    gkDuration_segC chnget "Duration_segC"
endif

if changed:k(chnget:k("SelectOsc_Loop"))==1 then
    gkSelectOsc_Loop chnget "SelectOsc_Loop"
endif

if changed:k(chnget:k("SelectOsc_LFO"))==1 then
    gkSelectOsc_LFO chnget "SelectOsc_LFO"
endif    

endin




;instrument will be triggered by keyboard widget
instr 2
kEnv madsr .1, .2, .6, .4

a3 vco2 p5, p4
a2 vco2 p5, p4*cent(400) , 2, 0.5
a1 oscil  p5, p4*cent(700)

iPt1    = 0     ; from 0 
iDur1   = .3       ;stay during this 
iPt2    = 1       ; to 0 and from 0
iDur2   = i(gkDuration_segA)  ; during this
iPt3    = 2         ;ramp to 1
iDur3   = 1       ; stay during this
iPt4    = 3         ;from THIS point after
iDur4   = i(gkDuration_segB)       ;sec
iPt5    = 4         ;REACH THIS POINT
iDur5   = 1      ; stay there 
iPt6    = 5       

kCurve linsegr iPt1 ,iDur1,   iPt2 , iDur2 ,iPt3,  iDur3  , iPt4  ,  iDur4 ,  iPt5 ,  iDur5 ,  iPt6  

if kCurve <1 then  ;plateau
    aOut = a1
elseif (kCurve >=1) && (kCurve <2) then   ;rampe
        aOut = (2-kCurve)* a1+ a2*(kCurve-1)
elseif (kCurve >=2) && (kCurve <3)then  ; plateau
    aOut = a2
elseif (kCurve >=3) && (kCurve <4)then ;rampe
   aOut = (4-kCurve)* a2+ a3*(kCurve-3)
elseif (kCurve >=4) then  ; plateau
   aOut = a3
endif 

outs aOut*kEnv, aOut*kEnv
endin

</CsInstruments>
<CsScore>
;causes Csound to run for about 7000 years...
f0 z
i 1 0 3600
</CsScore>
</CsoundSynthesizer>
`

For this sort of mechanism, there are a number of things that might be desirable:

  • That it can be extended easily to incorporate a larger number of input signals.
  • That alternative fade-curves can be employed - linear cross-fading tends to leave a perceptual ‘hole in the middle’.
  • ftmorf is quite nice but this limits the source signals to oscillators as opposed to any audio signal.

Here is what I came up with to address these ideas. It’s a little like Thrifleganger’s method but you only need to create a single crossfade function. There are four shapes suggested for cross-fading curves, I think Hanning sounds quite smooth.

<CsoundSynthesizer>

<CsOptions>
-dm0 -n
</CsOptions>

<CsInstruments>

sr 		= 	44100
ksmps 	= 	16
nchnls 	= 	2
0dbfs	=	1
 
instr		1
 ; create any number of signals...
 a1		poscil	0.2,200	
 a2		poscil	0.2,400
 a3		poscil	0.2,600
 a4		poscil	0.2,800
 a5		poscil	0.2,1000
 
 ; window shape used for fade-in/fade-out
 iShape	ftgen		0,0,4097,20,2,1		; hanning
 ;iShape	ftgen		0,0,4097,9,0.5,1,0	; half-sine / equal-power
 ;iShape	ftgen		0,0,4097,7,0,2048,1,2048,0		; linear
 ;iShape	ftgen		0,0,4097,20,6,1		; gaussian

 ; crossfader. Should start at zero and can extend in integer steps for the number of signals to be mixed
 kFade  line		0, p3, 4			; 0 = first signal only, 0.5 = mix of first two, 1 = second signal only etc...
  
 ; crossfade between any number of signals, note the sequence of table index offsets decreasing in integer steps
 aOut	sum		a1 * tablei:k( (kFade + 1) *0.5 , iShape, 1), \
 				a2 * tablei:k( (kFade + 0) *0.5 , iShape, 1), \
 				a3 * tablei:k( (kFade - 1) *0.5 , iShape, 1), \
 				a4 * tablei:k( (kFade - 2) *0.5 , iShape, 1), \
 				a5 * tablei:k( (kFade - 3) *0.5 , iShape, 1)	
 
 		outs		aOut, aOut				; send audio to outputs
 
endin

</CsInstruments>

<CsScore>
i 1 0 7
</CsScore>

</CsoundSynthesizer>

Thank you a lot Iain… I will try to understand it .
This indeed provides a very nice sounding morphing with the hanning window.

Hello.
Is it possible to do it with oscilikt opcode?
Regards.
J.

oscilikt does indeed allow changing of function tables at k-rate but it doesn’t provide any interpolation or crossfading between tables - the function table used by the oscillator will simply change abruptly.

You can also use two parallel, swapping oscilikts and cyclically crossfade between them:

<Cabbage>
form caption("Morph") size(190,120), colour(0,0,0) pluginID("CrFa")
rslider    bounds(55,20, 80, 80), channel("Crossfade"), range(0, 19, 0) textbox(1) valuetextbox(1)
</Cabbage>

<CsoundSynthesizer>

<CsOptions>
-dm0 -n
</CsOptions>

<CsInstruments>

sr 		= 	44100
ksmps 	= 	16
nchnls 	= 	2
0dbfs	=	1

; function tables: ascending partials
i_  ftgen   1,0,4097,9,1,1,0
i_  ftgen   2,0,4097,9,2,1,0
i_  ftgen   3,0,4097,9,3,1,0
i_  ftgen   4,0,4097,9,4,1,0
i_  ftgen   5,0,4097,9,5,1,0
i_  ftgen   6,0,4097,9,6,1,0
i_  ftgen   7,0,4097,9,7,1,0
i_  ftgen   8,0,4097,9,8,1,0
i_  ftgen   9,0,4097,9,9,1,0
i_  ftgen   10,0,4097,9,10,1,0
i_  ftgen   11,0,4097,9,11,1,0
i_  ftgen   12,0,4097,9,12,1,0
i_  ftgen   13,0,4097,9,13,1,0
i_  ftgen   14,0,4097,9,14,1,0
i_  ftgen   15,0,4097,9,15,1,0
i_  ftgen   16,0,4097,9,16,1,0
i_  ftgen   17,0,4097,9,17,1,0
i_  ftgen   18,0,4097,9,18,1,0
i_  ftgen   19,0,4097,9,19,1,0
i_  ftgen   20,0,4097,9,20,1,0

 ; window shape used for fade-in/fade-out
 giShape	ftgen		0,0,4097,20,2,1		; hanning

instr		1
 kFade  port    chnget:k("Crossfade"),1/ksmps ; a table index starting at zero
 
 kCPS   =          110 ; frequency of oscillator
 a1     oscilikt   0.1,kCPS, 1 + (round(kFade*0.5) * 2)
 a2     oscilikt   0.1,kCPS, 2 + (int(kFade*0.5) * 2)

 aMix   sum         a1*a(tablei:k(kFade*0.5,giShape,1,0.5,1)), \
                    a2*a(tablei:k(kFade*0.5,giShape,1,0,1))
 		outs		aMix, aMix
endin

</CsInstruments>

<CsScore>
i 1 0 36000
</CsScore>

</CsoundSynthesizer>

Dear All,

Many thanks for your answers. I sill need an advice about sound morphing:
I am writing a midi synth with 3 oscil using waveshapes, each one, his own sound, a1,a2,a3
Each sound can be played durng t1, t2 and t3
Then between these, there can be ramps during which the morphing happens a1->2, t4 and a2->3,t5
So the sequence is

[a1,t1], [a1->2, t4] , [a2,t2], [a2->3,t5], [a3,t3]

ATM, I use a linsegr for morphing between them as it is easy but I hear that when the midi note is realease, the linesegr rushed to the end of the sequence, therefore if a short note is played , lineseg goes through all the 3 sounds… and I am expecting to simply end the sequence where it is. .
For instance, considering the following durations,
t1 = 5 s, t4 = 2s , t2 =2s, t5 = 2s, t3 = 4s ( or more… until release)
Playing a note of duration 2sec, when release , I would like to hear only sound a1 ( with the enveloppe applied to it ).
If note is released at 6s, then the synth should end playing the transition between a1 and a2.

( I hope I am clear enough )
Do you know how I could do ?
Thank you for help

I’m not totally sure how you have built this, but if you want a linseg to simply freeze its value during a note’s release, you can skip it whenever the release flag is sensed:

<Cabbage>
form caption("Untitled") size(400, 80), colour(58, 110, 182), pluginID("def1")
keyboard bounds(0,0,400,80)
</Cabbage>
<CsoundSynthesizer>
<CsOptions>
-n -d -+rtmidi=NULL -M0 -m0d --midi-key-cps=4 --midi-velocity-amp=5
</CsOptions>
<CsInstruments>
; Initialize the global variables. 
sr = 44100
ksmps = 32
nchnls = 2
0dbfs = 1

gi1 ftgen 0, 0, 4096, 10, 1
gi2 ftgen 0, 0, 4096, 10, 0, 1
gi3 ftgen 0, 0, 4096, 10, 0, 0, 1
gitabs ftgen 0, 0, 4, -2, gi1, gi2, gi3, gi3

instr 1
 ires   ftgentmp 0, 0, 4096, 2, 0 ; create a 'local' function table for this note
 if release:k()==0 then ; only perform the following line when the note is held
  kftndx linseg   0, 1, 1, 1, 2 ; morphing index
 endif
        ftmorf   kftndx, gitabs, ires ; create 'local' morphed function table
 aenv   linsegr  1, 3, 0 ; amplitude envelope
 asig   oscil    p5*aenv, p4, ires ; oscillator
        outs     asig, asig
endin

</CsInstruments>
<CsScore>
f0 z
</CsScore>
</CsoundSynthesizer>

Hello Iain,

Well it is tricky to add part of the code but here it is with some explanations :
The user can select either :

  • A loop over the 3 sound textures/timbre a1,a2 and a3 ( and not notes) , this is based on a loopseg
  • A LFO cycling through the 3 sounds texture
  • Or not cycle at all, starting with one of the textures and going over the two other ones.
    The code works with all 3 modes (lfo, loopseg, no cycle)
    For achieving this :
    a1,a2 and a3 durations are respectively called gkDuration PallierA, gkDuration PallierB, gkDuration PallierC below
    For reaching a2 from a1, there is a ramp during which a weighted average is calculated upon time .
    The duration is gkDuration_segA.
    From a2 to a3, the ramp last gkDuration_segB.

So a lineseg is made from 0 to 5, each intergar value represent a change :
In interval [0, 1[ : play a1 only ; [1, 2[ : make a ramp between a1 and a2, [2, 3[ : play a2 only , [3, 4[ : make a ramp between a2 and a3, and [4, 5[ : play a3 only until released.

kFreq =gkTempo/(gkDuration_PallierA+gkDuration_segA+gkDuration_PallierB+gkDuration_segB+gkDuration_PallierC)
if gkSelectOsc_LFO==0 then
    if gkSelectOsc_Loop==1 then
        kCurve loopseg kFreq, 0, 0, 0,0,0, gkDuration_PallierA,1, gkDuration_segA, 2, gkDuration_PallierB, 3, gkDuration_segB, 4, gkDuration_PallierC ,4 ,0,-1,gkDuration_PallierA,0   ; ,4,gkDuration_segB ,3,gkDuration_PallierB,2,gkDuration_segA,1,gkDuration_PallierA/2,0
    else
        kCurve linsegr 0 , i(gkDuration_PallierA), 1, i(gkDuration_segA), 2, i(gkDuration_PallierB), 3, i(gkDuration_segB), 4, i(gkDuration_PallierC), 5      
    endif
else
    kCurve  lfo 1, gkTempo/gkDuration_segA 
endif

if gkSelectOsc_LFO==0 then
    k1 =kCurve
    if  (0 >=kCurve) && (kCurve >-1) then
        aOutL sum abs(kCurve)*aA3L ,  (kCurve+1)*aA1L 
        aOutR sum abs(kCurve)*aA3R ,  (kCurve+1)*aA1R 
    elseif  (1 >=kCurve) && (kCurve >0) then
        aOutL = aA1L
        aOutR = aA1R
    elseif (2 >=kCurve) && (kCurve >1) then
        aOutL sum (2-kCurve)*aA1L ,  (kCurve-1)*aA2L
        aOutR sum (2-kCurve)*aA1R ,  (kCurve-1)*aA2R
    elseif (3 >=kCurve) && (kCurve >2) then
        aOutL = aA2L  
        aOutR = aA2R
    elseif (4 >=kCurve) && (kCurve >3) then
        aOutL sum (4-kCurve)*aA2L ,  (kCurve-3)*aA3L
        aOutR sum (4-kCurve)*aA2R ,  (kCurve-3)*aA3R
    elseif (5 >=kCurve) && (kCurve >4) then 
        aOutL = aA3L
        aOutR = aA3R  
    endif
else            ;LFO sinus  for now not flexible (Freq is steady..
    if kCurve>=0 then
        aOutL = (1-kCurve)*aA2L + kCurve*aA1L
        aOutR = (1-kCurve)*aA2R + kCurve*aA1R
    else
        aOutL = abs(1+kCurve)*aA2L + abs(kCurve)*aA3L
        aOutR = abs(1+kCurve)*aA2R + abs(kCurve)*aA3R
    endif
endif 
 aOutL sum aOutL*kOscA_Env*gkOscA_Gain
 aOutR sum aOutR*kOscA_Env*gkOscA_Gain
else
    aOutL sum aOscA * (1-gkOscA_Pan) *kOscA_Env*gkOscA_Gain,  aOscB* (1-gkOscB_Pan)*kOscB_Env*gkOscB_Gain,     aOscC*(1- gkOscC_Pan)*kOscC_Env*gkOscC_Gain, aOscD*kNoise_Env
    aOutR sum aOscA* gkOscA_Pan *kOscA_Env*gkOscA_Gain,  aOscB* gkOscB_Pan*kOscB_Env*gkOscB_Gain,  aOscC* gkOscC_Pan*kOscC_Env*gkOscC_Gain,  aOscD *kNoise_Env

endif

The code I included in the previous email should do what you need. You just need to encapsulate the code that creates kCurve in the conditional so that it is skipped when the note is released.
Here it is with the code you just posted:

if release:k()==1 kgoto SKIP
kFreq =gkTempo/(gkDuration_PallierA+gkDuration_segA+gkDuration_PallierB+gkDuration_segB+gkDuration_PallierC)
if gkSelectOsc_LFO==0 then
    if gkSelectOsc_Loop==1 then
        kCurve loopseg kFreq, 0, 0, 0,0,0, gkDuration_PallierA,1, gkDuration_segA, 2, gkDuration_PallierB, 3, gkDuration_segB, 4, gkDuration_PallierC ,4 ,0,-1,gkDuration_PallierA,0   ; ,4,gkDuration_segB ,3,gkDuration_PallierB,2,gkDuration_segA,1,gkDuration_PallierA/2,0
    else
        kCurve linsegr 0 , i(gkDuration_PallierA), 1, i(gkDuration_segA), 2, i(gkDuration_PallierB), 3, i(gkDuration_segB), 4, i(gkDuration_PallierC), 5      
    endif
else
    kCurve  lfo 1, gkTempo/gkDuration_segA 
endif
SKIP:

if gkSelectOsc_LFO==0 then
    k1 =kCurve
    if  (0 >=kCurve) && (kCurve >-1) then
        aOutL sum abs(kCurve)*aA3L ,  (kCurve+1)*aA1L 
        aOutR sum abs(kCurve)*aA3R ,  (kCurve+1)*aA1R 
    elseif  (1 >=kCurve) && (kCurve >0) then
        aOutL = aA1L
        aOutR = aA1R
    elseif (2 >=kCurve) && (kCurve >1) then
        aOutL sum (2-kCurve)*aA1L ,  (kCurve-1)*aA2L
        aOutR sum (2-kCurve)*aA1R ,  (kCurve-1)*aA2R
    elseif (3 >=kCurve) && (kCurve >2) then
        aOutL = aA2L  
        aOutR = aA2R
    elseif (4 >=kCurve) && (kCurve >3) then
        aOutL sum (4-kCurve)*aA2L ,  (kCurve-3)*aA3L
        aOutR sum (4-kCurve)*aA2R ,  (kCurve-3)*aA3R
    elseif (5 >=kCurve) && (kCurve >4) then 
        aOutL = aA3L
        aOutR = aA3R  
    endif
else            ;LFO sinus  for now not flexible (Freq is steady..
    if kCurve>=0 then
        aOutL = (1-kCurve)*aA2L + kCurve*aA1L
        aOutR = (1-kCurve)*aA2R + kCurve*aA1R
    else
        aOutL = abs(1+kCurve)*aA2L + abs(kCurve)*aA3L
        aOutR = abs(1+kCurve)*aA2R + abs(kCurve)*aA3R
    endif
endif 
 aOutL sum aOutL*kOscA_Env*gkOscA_Gain
 aOutR sum aOutR*kOscA_Env*gkOscA_Gain
else
    aOutL sum aOscA * (1-gkOscA_Pan) *kOscA_Env*gkOscA_Gain,  aOscB* (1-gkOscB_Pan)*kOscB_Env*gkOscB_Gain,     aOscC*(1- gkOscC_Pan)*kOscC_Env*gkOscC_Gain, aOscD*kNoise_Env
    aOutR sum aOscA* gkOscA_Pan *kOscA_Env*gkOscA_Gain,  aOscB* gkOscB_Pan*kOscB_Env*gkOscB_Gain,  aOscC* gkOscC_Pan*kOscC_Env*gkOscC_Gain,  aOscD *kNoise_Env

endif

It works like a charm ! Thank you a lot Iain !