Showing posts with label analogue. Show all posts
Showing posts with label analogue. Show all posts

Thursday, 12 February 2015

Surprising Success With The Waldorf

I'll be honest, today I tried and failed to synthesis Well Tempered Clavier part 2. But, as a consolation prise I got a lovely deep 'cello'.


Now, instruments of the violin family cannot be synthesised in any realistic way; they are just too complex (if someone does it - they are probably using samples directly - convolution with a sample also works). However, one can (I believe) capture their essence using first principles synthesis. The key ingredients are:

  1. Body resonance - if you think you have too much - you probably don't.
  2. Pitch variation due to bowing (as the string in stretched by the bow its pitch varies).
  3. A shockingly rattle at the top end which somehow works out because it is unstable.
  4. A shimmering stereo field cause but the sound being sent in all directions by the body of the instrument.
  5. A strong variation of the notes as they move in and out of resonance with the body.
  6. A very carefully tailored envelope.
  7. Vibrato and tremolo which is not just slapped on but moves and is subtle.

This is the Sonic Field blog so which of these came from the Waldorf Pulse 2 and which from Sonic Field?

2 and 3 were definitely aided by Sonic Field. The reverb had a chunk of excitation to it which built on top of chorusing. This along with the alive nature of the synth (being analogue) helped create this piece. It is not a cello but people tell me it sounds nice :)


Tuesday, 10 February 2015

Waldorf Pulse 2: A Bit Of Random - A Lot Of Fun

Right now I am completely loving working with the Waldorf - the harpsichord sound it can make is beyond belief. 

A regular old fashioned ring modulated synth-harpsichord is all very well; but the pulse width modulated effect which is possible with the Waldorf is something else again. The raw sounds from the little synth is a bit rough and a bit electronic but when passed through a touch of harmonic excitation and reverb in Sonic Field the result is stunning. Now, I might be blowing my own trumpet, but I can honestly say everyone who has listened to this live has praised the sound:


To be completely honest, I was a bit lucky. I just tried adding a bit of spring reverberation (using a spring impulse response) and I think that was the final trick to make the sound come to life. The reverb' you can here is a mixture of a few room/hall impulse responses reverbs mixed with a bit of spring.

But, the true secret is the way the signal path of a true analogue synth works. The sounds are all coupled and constantly changing. An electronic audio circuit 'wants' to make audio because the values of the components are set up that way. The circuits in an analogue synth' then interact with one another in music ways. This is distinctly different from pure digital synthesis where nice sounding audio is something one has to force from the algorithms. I am enjoying the mix where the analogue makes amazing feed stuff for digital post processing.

Tuesday, 3 February 2015

Working With An Analogue SynthThe

The Waldorf Pulse 2 analogue synthesiser

Most of my work in Sonic Field has been using the built in synth abilities of the program. But there is not reason it should to drive an external synth and post process the signal.

I recently bought a Pulse 2 and it is quite amazing. However, it is also a mono synth. I am completely spoilt generating sounds with Sonic Field as it has no upper limit to the number of notes which can be generated at once. Whilst the mono synth sounds has its place, it is also rather limited. So, I needed a solution to give multi-tracking.

The existing midi implementation in SF was just pathetic. I completely ripped it out and pretty much started over. The only piece remaining is the code which maps midi on/off messages into notes and disambiguated overlapping messages on the same track/channel/key combination.

I should go into great detail about how it all works, but I am exhausted after a long day working and evening making music so here is the dump of the patch I used to drive the synthesiser over midi. Yes - I drove the synth from Sonic Field directly!



from com.nerdscentral.audio.midi import MidiFunctions

class Midi(MidiFunctions):
    metaTypes={
            0x00:'SequenceNumber',
            0x01:'text',
            0x02:'copyright',
            0x03:'track_name',
            0x04:'instrument',
            0x05:'lyrics',
            0x06:'marker',
            0x07:'cue',
            0x20:'channel',
            0x2F:'end',
            0x51:'tempo',
            0x54:'smpte_offset',
            0x58:'time_signature',
            0x59:'key_signature',
            0x7f:'sequencer_specific'
        }
        
    timeTypes={
        0.0:  'PPQ',
        24.0: 'SMPTE_24',
        25.0: 'SMPTE_25',
        29.97:'SMPTE_30DROP',
        30.0: 'SMPTE_30'
    }
     
    @staticmethod
    def timeType(sequence):
        return Midi.timeTypes[sequence.getDivisionType()]

    @staticmethod
    def isNote(event):
        return event['command']=='note'

    @staticmethod
    def isMeta(event):
        return event['command']=='meta'

    @staticmethod
    def isCommand(event):
        return event['command']=='command'
        
    @staticmethod
    def isTempo(event):
        Midi.checkMeta(event)
        return event['type']==0x51

    @staticmethod
    def isTimeSignature(event):
        Midi.checkMeta(event)
        return event['type']==0x58

    @staticmethod
    def metaType(event):
        t=event['type']
        if t in Midi.metaTypes:
            return Midi.metaTypes[t]
        return 'unknown'

    @staticmethod
    def checkMeta(event):
        if not event['command']=='meta':
            raise Exception('Not meta message')

    @staticmethod
    def tempo(event):
        Midi.checkMeta(event)
        if event['type']!=0x51:
            raise Exception('not tempo message')
        data=event['data']
        if len(data)==0:
            raise Exception('no data')
        t=0
        for i in range(0,len(data)):
            if not i==0:
                t <<= 8
            t+=data[i]
        return t

    @staticmethod
    def timeSignature(event):
        Midi.checkMeta(event)
        if event['type']!=0x58:
            raise Exception('not tempo message')
        data=event['data']
        if not len(data)==4:
            raise Exception('wrong data')
        return {
            'numerator'  :data[0],
            'denominator':2**data[1],
            'metronome'  :data[2],
            '32nds/beat' :data[3]
        }
        
    @staticmethod
    def tickLength(denominator,microPerQuater,sequence):
        # if denom = 4 then 1 beat per quater note
        # if denom = 8 then 2 beats per quater note
        # there fore beats per quater note= denom/4
        beatsPerQuaterNote = denominator/4.0
        ticksPerBeat       = float(sequence.getResolution())
        microsPerBeat      = float(microPerQuater)/beatsPerQuaterNote
        return microsPerBeat/float(ticksPerBeat)

sequence=Midi.readMidiFile("temp/passac.mid")

print 'Sequence Time  Type:', Midi.timeType(sequence)
print 'Sequence Resolution:', sequence.getResolution()
print 'Initial tick length:',Midi.tickLength(4,500000,sequence)
otl=Midi.tickLength(4,500000,sequence)

midis=Midi.processSequence(sequence)

sout=Midi.blankSequence(sequence)

# Create the timing information track
tout=sout.createTrack()
for event in midis[0]:
    if Midi.isMeta(event):
        if Midi.isTempo(event) or Midi.isTimeSignature(event):
            tout.add(event['event'])

tout1=sout.createTrack()
tout2=sout.createTrack()
midi1=[]
midi2=[]
flip=True
minKey=999
maxKey=0

# Use 499 for 1 Done
# Use 496 for 2
# Use 497 for 3
# Use 497 for 4
# Use 001 for 5 Done
# Use 002 for 6
midiNo=6

for event in midis[midiNo]:
    if Midi.isNote(event):
        ev1=event['event']
        ev2=event['event-off']
        if event['key']>maxKey:
            maxKey=event['key']
        if event['key']<minKey:
            minKey=event['key']

for event in midis[midiNo]:
    if Midi.isNote(event):
        ev1=event['event']
        ev2=event['event-off']
        ev1.setTick(ev1.getTick()+600)
        ev2.setTick(ev2.getTick()+600)
        key=event['key']
        pan=127.0*float(key-minKey)/float(maxKey-minKey)
        pan=31+pan/2
        pan=int(pan)
        pan=Midi.makePan(1,ev1.getTick()-1,pan)
        if flip:
            midi1.append(pan)
            midi1.append(event['event'])
            midi1.append(event['event-off'])
            flip=False
        else:
            midi2.append(pan)
            midi2.append(event['event'])
            midi2.append(event['event-off'])
            flip=True

Midi.addPan(tout1,1,100,64)
Midi.addPan(tout2,2,100,64)

Midi.addNote(tout1,1,100,120,50,100)
Midi.addNote(tout2,2,100,120,50,100)
        
midi1=sorted(midi1,key=lambda event: event.getTick())
midi2=sorted(midi2,key=lambda event: event.getTick())

for event in midi1:
    Midi.setChannel(event,1)
    tout1.add(event)
#for event in midi2:
#    Midi.setChannel(event,2)
#    tout2.add(event)

Midi.writeMidiFile("temp/temp.midi",sout)

for dev in Midi.getMidiDeviceNames():
    print dev

player=Midi.getPlayer(3,2)
player.manual(sout)
player.waitFor()

And here is the post processing patch. I took each separately recorded voice from the synth and mixed them together in Audacity using the note I injected at a known point at the start of each to line them up. Once the mix sounded OK, I post processed with this patch:

def reverbInner(signal,convol,grainLength):
    def rii():
        mag=sf.Magnitude(+signal)
        if mag>0:
            signal_=sf.Concatenate(signal,sf.Silence(grainLength))
            signal_=sf.FrequencyDomain(signal_)
            signal_=sf.CrossMultiply(convol,signal_)
            signal_=sf.TimeDomain(signal_)
            newMag=sf.Magnitude(+signal_)
            if newMag>0:
                signal_=sf.NumericVolume(signal_,mag/newMag)        
                # tail out clicks due to amplitude at end of signal 
                return sf.Realise(signal_)
            else:
                return sf.Silence(sf.Length(signal_))
        else:
            -convol
            return signal
    return sf_do(rii)
            
def reverberate(signal,convol):
    def revi():
        grainLength = sf.Length(+convol)
        convol_=sf.FrequencyDomain(sf.Concatenate(convol,sf.Silence(grainLength)))
        signal_=sf.Concatenate(signal,sf.Silence(grainLength))
        out=[]
        for grain in sf.Granulate(signal_,grainLength):
            (signal_i,at)=grain
            out.append((reverbInner(signal_i,+convol_,grainLength),at))
        -convol_
        return sf.Clean(sf.FixSize(sf.MixAt(out)))
    return sf_do(revi)

def excite(sig_,mix,power):
    def exciteInner():
        sig=sig_
        m=sf.Magnitude(+sig)
        sigh=sf.BesselHighPass(+sig,500,2)
        mh=sf.Magnitude(+sigh)
        sigh=sf.Power(sigh,power)
        sigh=sf.Clean(sigh)
        sigh=sf.BesselHighPass(sigh,1000,2)
        nh=sf.Magnitude(+sigh)
        sigh=sf.NumericVolume(sigh,mh/nh)
        sig=sf.Mix(sf.NumericVolume(sigh,mix),sf.NumericVolume(sig,1.0-mix))
        n=sf.Magnitude(+sig)
        return sf.Realise(sf.NumericVolume(sig,m/n))
    return sf_do(exciteInner)

####################################
#
# Load the file and clean
#
####################################

(left,right)=sf.ReadFile("temp/pulse-passa-2.wav")

left =sf.Multiply(sf.NumericShape((0,0),(64,1),(sf.Length(+left ),1)),left )
right=sf.Multiply(sf.NumericShape((0,0),(64,1),(sf.Length(+right),1)),right)

left =sf.Concatenate(sf.Silence(1024),left)
right=sf.Concatenate(sf.Silence(1024),right)


####################################
#
# Room Size And Nature Controls
#
####################################

bright  = True
vBright = False
church  = False
ambient = False
post    = True
spring  = False
bboost  = False
  
if ambient:  
    (convoll,convolr)=sf.ReadFile("temp/v-grand-l.wav")
    (convorl,convorr)=sf.ReadFile("temp/v-grand-r.wav")
elif church:    
    (convoll,convolr)=sf.ReadFile("temp/bh-l.wav")
    (convorl,convorr)=sf.ReadFile("temp/bh-r.wav")
else:
    (convoll,convolr)=sf.ReadFile("temp/Vocal-Chamber-L.wav")
    (convorl,convorr)=sf.ReadFile("temp/Vocal-Chamber-R.wav")

if spring:
    spring=sf.ReadFile("temp/classic-fs2a.wav")[0]
    convoll=sf.Mix(
        convoll,
        +spring
    )
    
    convorr=sf.Mix(
        convorr,
        sf.Invert(spring)
    )

if bboost:
    left =sf.RBJLowShelf(left,256,1,6)
    right=sf.RBJLowShelf(right,256,1,6)
    
convoll=excite(convoll,0.75,2.0)
convolr=excite(convolr,0.75,2.0)
convorl=excite(convorl,0.75,2.0)
convorr=excite(convorr,0.75,2.0)

ll  = reverberate(+left ,convoll)
lr  = reverberate(+left ,convolr)
rl  = reverberate(+right,convorl)
rr  = reverberate(+right,convorr)
wleft =sf.FixSize(sf.Mix(ll,rl))
wright=sf.FixSize(sf.Mix(rr,lr))

wright = excite(wright,0.15,1.11)
wleft  = excite(wleft ,0.15,1.11)

if bright:
    right  = excite(right,0.15,1.05)
    left   = excite(left ,0.15,1.05)
if vBright:
    right  = excite(right,0.25,1.15)
    left   = excite(left ,0.25,1.15)

sf.WriteFile32((sf.FixSize(+wleft),sf.FixSize(+wright)),"temp/wet.wav")

wleft =sf.FixSize(sf.Mix(sf.Pcnt15(+left),sf.Pcnt85(wleft)))
wright =sf.FixSize(sf.Mix(sf.Pcnt15(+right),sf.Pcnt85(wright)))

sf.WriteFile32((+wleft,+wright),"temp/mix.wav")

if ambient:
    (convoll,convolr)=sf.ReadFile("temp/ultra-l.wav")
    (convorl,convorr)=sf.ReadFile("temp/ultra-r.wav")
elif church:
    (convoll,convolr)=sf.ReadFile("temp/v-grand-l.wav")
    (convorl,convorr)=sf.ReadFile("temp/v-grand-r.wav")
else:
    (convoll,convolr)=sf.ReadFile("temp/bh-l.wav")
    (convorl,convorr)=sf.ReadFile("temp/bh-r.wav")

left  = sf.BesselLowPass(left  ,392,1)
right = sf.BesselLowPass(right,392,1)
ll  = reverberate(+left ,convoll)
lr  = reverberate( left ,convolr)
rl  = reverberate(+right,convorl)
rr  = reverberate( right,convorr)
vwleft =sf.FixSize(sf.Mix(ll,rl))
vwright=sf.FixSize(sf.Mix(rr,lr))
sf.WriteFile32((sf.FixSize(+vwleft),sf.FixSize(+vwright)),"temp/vwet.wav")
wleft =sf.FixSize(sf.Mix(wleft ,sf.Pcnt20(vwleft )))
wright=sf.FixSize(sf.Mix(wright,sf.Pcnt20(vwright)))
sf.WriteSignal(+wleft ,"temp/grand-l.sig")
sf.WriteSignal(+wright,"temp/grand-r.sig")
wleft  = sf.Normalise(wleft)
wright = sf.Normalise(wright)
sf.WriteFile32((wleft,wright),"temp/grand.wav")

if post:
    print "Warming"
    
    left  = sf.ReadSignal("temp/grand-l.sig")
    right = sf.ReadSignal("temp/grand-r.sig")
    
    def highDamp(sig,freq,fact):
        hfq=sf.BesselHighPass(+sig,freq,4)
        ctr=sf.FixSize(sf.Follow(sf.FixSize(+hfq),0.25,0.5))
        ctr=sf.Clean(ctr)
        ctr=sf.RBJLowPass(ctr,8,1)
        ctr=sf.DirectMix(
            1,
            sf.NumericVolume(
                sf.FixSize(sf.Invert(ctr)),
                fact
            )
        )
        hfq=sf.Multiply(hfq,ctr)
        return sf.Mix(hfq,sf.BesselLowPass(sig,freq,4))
    
    def filter(sig_):
        def filterInner():
            sig=sig_
            q=0.5
            sig=sf.Mix(
                sf.Pcnt10(sf.FixSize(sf.WaveShaper(-0.03*q,0.2*q,0,-1.0*q,0.2*q,2.0*q,+sig))),
                sig
            )
            sig=sf.RBJPeaking(sig,64,2,2)
            damp=sf.BesselLowPass(+sig,2000,1)
            sig=sf.FixSize(sf.Mix(damp,sig))
            low=sf.BesselLowPass(+sig,256,4)
            m1=sf.Magnitude(+low)
            low=sf.FixSize(low)
            low=sf.Saturate(low)
            m2=sf.Magnitude(+low)
            low=sf.NumericVolume(low,m1/m2)
            sig=sf.BesselHighPass(sig,256,4)
            sig=sf.Mix(low,sig)
            sig=highDamp(sig,5000,0.66)
            return sf.FixSize(sf.Clean(sig))
        return sf_do(filterInner)
    
    left  = filter(left)
    right = filter(right)
    sf.WriteFile32((left,right),"temp/proc.wav")


Sunday, 2 March 2014

Python: Creating Oscillators In Python

What is an Oscillator and how can we great one using a generator in Python?

An oscillator is something which naturally passes back and forth through some fixed or semi-fixed pattern. A simple but effective transistor based oscillator is a phase delay (or phase shift) circuit:
Wiki Commons - see here
http://en.wikipedia.org/wiki/File:RC_phase_shift_oscillator.svg 

The output is delayed and fed back into the input. The output is the inverse of the input. This means that without the delay the circuit would do nothing at all. However, because there is a delay it oscillates making a sine wave. We can make a Python generator do very much the same thing:

from com.nerdscentral.audio import SFData

def oscillator(damping):
    damping = float(damping)
    weight  =  0.1
    value   =  0.0
    middle  = value
    
    yield 0,0
        
    while(1):
        if(value>middle):
            weight-=damping
        else:
            weight+=damping

        value+=weight
                
        yield value,weight

It almost looks too simple to work but it does. The phase shift delay is not caused by 'recording' a sequence of output values and feeding them into the input (inverted). It is done by making the feedback cumulative. The variable weight is slowly shifted to oppose the variable value. If we plot the two variables as waves we get this:

Waveforms of weight and value.
value top.
weight bottom.
These are not plotted to amplitude scale.
The max value of wave is  ca 50 and the
max value of weight is ca .01
We can see from the above that weight is 90 degrees out of phase with value. We have made a phase delay oscillator. This approach makes a passible sine wave: However, the amplitude is not controlled at all that the produced wave for is not a very good sine wave. 

The spectrum of our oscillator output. The large number and
magnitude of harmonics shows it not to be a very pure
sine wave.
We can improve the stability and quality a lot with a simple addition:

def oscillator(damping):
    damping = float(damping)
    lower   = -1.0
    upper   =  1.0
    weight  =  0.1
    value   =  0.0
    middle  = value
    
    yield 0,0
        
    while(1):
        if(value>middle):
            weight-=damping
        else:
            weight+=damping

        value+=weight
                
        yield out,weight
        if(out<lower):
            value=prev
        elif(out>upper):
            value=prev

This addition locks the oscillator between +-1 and improves the sine wave quite a bit. The new spectrum looks like this (it is higher frequency for the given damping):

Slightly enhanced oscillator spectrum

Let's Make Some Sounds

Making oscillators is fun, but now we have an analogue style oscillator in Python, we really have a moral responsibility to make sounds with it! Will the computational equivalent of analogue make more complex and interesting signals and traditional digital stuff? Here is a much more interesting version of the oscillator:

def oscilator(damping,asym=1.0,mixer=0):
    damping = float(damping)
    lower   = -1.0
    upper   =  1.0
    weight  =  0.1
    value   =  0.0
    middle  = value
    gain    = 1.0
    prev    = 0.0
    cross   = 0.0
    pos     = 0.0
    gainV   = 0.9999
    xcount  = 0
    asym    = float(asym)
    
    yield 0,0,0
        
    while(1):
        if(value>middle):
            weight-=damping*asym
        else:
            weight+=damping

        if(mixer != 0):
            value+=mixer.next()
            
        value+=weight
        
        out=value*gain
        
        yield out,weight,xcount

        if(out<lower):
            value=prev
            gain*=gainV
        elif(out>upper):
            value=prev
            gain*=gainV
        elif(prev>0 and value<0):
            gain/=gainV
            xcount+=1
         
        pos+=1
        prev=value

def wobble(damping):
    wosc=oscilator(damping,1.0)
    while(1):
        s,t,xs=wosc.next()
        yield s*0.00001

The above uses recursive generators to make one oscillator inject instability into a second. We pass an instance of wobble into the mixer parameter of oscillator to get the effect. I have also added the ability to inject asymmetry into the oscillator to add harmonics. In have highlighted the bits of code which do these things.

We can put the output of our oscillator into a Sonic Field SFData object and then process it like any other sound:

from com.nerdscentral.audio import SFData
...
            data=SFData.build(len)
            for x in range(0,length):
                s,t,xs=osc.next()
                data.setSample(x,s)

Yes - it really is that simple to create an auto signal from a Python generator using Sython.

Warning - read the label carefully:

If you are  familiar with the determinism of working with normal digital signals, this approach will come as a bit of a shock. What you end up with is unstable and pretty much unpredictable. Though the output signal is deterministic (you run it twice you get the same numbers) it is also highly unstable. That really nice sine wave I showed above is a 'attractor' for the equation. It is a well behaved oscillating attractor. What you get with the more complex recursive version is a 'strange attractor'; the signal does not repeat it self. It might not even be a real attractor but just a semi-stable state from which, after enough cycles, the system will escape. Also, forget normal tuning, the output frequency is not linearly dependant on the input one. To get any sort of accurate pitch I would suggest counting the crossovers and then changing the sample rate to lock the pitch to that required.

First Creation:


Above is the first creation I have made with this new technique. It is not music at all. I wanted to create a sound into which the listener is placed which conveys the menace of WWII era piston engine aircraft. The very rich and ever changing 'analogue' nature of the oscillators does this in a way much more convincing that I think I could have managed using the normal sine wave generator and post processing approach of digital synthesis (or at least, not as easily).

Here is the patch which created the piece:


import math
import random
from com.nerdscentral.audio import SFData
    
def fixSize(signal):
    mag=sf.MaxValue(signal)
    return sf.NumericVolume(signal,1.0/mag)

def nullMixer():
    while(1):
        yield 0

def oscilator(damping,asym=1.0,mixer=0):
    damping = float(damping)
    lower   = -1.0
    upper   =  1.0
    weight  =  0.1
    value   =  0.0
    middle  = value
    gain    = 1.0
    prev    = 0.0
    cross   = 0.0
    pos     = 0.0
    gainV   = 0.9999
    xcount  = 0
    asym    = float(asym)
    
    yield 0,0,0
        
    while(1):
        if(value>middle):
            weight-=damping*asym
        else:
            weight+=damping

        if(mixer != 0):
            value+=mixer.next()
            
        value+=weight
        
        out=value*gain
        
        yield out,weight,xcount

        if(out<lower):
            value=prev
            gain*=gainV
        elif(out>upper):
            value=prev
            gain*=gainV
        elif(prev>0 and value<0):
            gain/=gainV
            xcount+=1
         
        pos+=1
        prev=value

def wobble(damping):
    wosc=oscilator(damping,1.0)
    while(1):
        s,t,xs=wosc.next()
        #print s
        yield s*0.00001

def invasion(d1,d2,seconds): 
    osc1=oscilator(d1,2,wobble(0.000020))
    osc2=oscilator(d1,2,wobble(0.000015))
    osc3=oscilator(d1,2,wobble(0.000010))
    osc4=oscilator(d1,2,wobble(0.000005))
    
    osc5=oscilator(d2,1.5,wobble(0.000020))
    osc6=oscilator(d2,1.5,wobble(0.000015))
    osc7=oscilator(d2,1.5,wobble(0.000010))
    osc8=oscilator(d2,1.5,wobble(0.000005))
        
    length=96000*seconds
    
    xs=0
    def drone(osc,len):
        def doDrone():
            data=SFData.build(len)
            print "Doing Drone"
            for x in range(0,length):
                s,t,xs=osc.next()
                data.setSample(x,s)
            # Go to a lot of effort to remove
            # clicks due to DC offset of the start and end
            l=sf.Length(data)
            data=sf.ButterworthHighPass(sf.Normalise(data),10,2)
            data=sf.Multiply(
                data,
                sf.NumericShape((0,0),(256,0),(l/2,1),(l-256,0),(l,0))
            )
            data=sf.Multiply(
                sf.Saturate(data),
                sf.NumericShape((0,0),(256,1),(l-256,1),(l,0))
            )
            return sf.Realise(data)
        return sf_do(doDrone)
    
    data1=drone(osc1,length)
    data2=drone(osc2,length)
    data3=drone(osc3,length)
    data4=drone(osc4,length)
    data5=drone(osc5,length)
    data6=drone(osc6,length)
    data7=drone(osc7,length)
    data8=drone(osc8,length)
    
    def mix1():
        return sf.Realise(
            fixSize(
                sf.MixAt(
                    (sf.Pcnt10(data2),30),
                    (sf.Pcnt20(data3),20),
                    (data1,0),
                    (data4,0),
                    (sf.Pcnt10(data6),30),
                    (sf.Pcnt20(data7),20),
                    (data5,0),
                    (data8,0)
                )
            )
        )
    
    def mix2():
        return sf.Realise(
            fixSize(
                sf.MixAt(
                    (sf.Pcnt10(data1),30),
                    (sf.Pcnt20(data4),20),
                    (data2,0),
                    (data3,0),
                    (sf.Pcnt10(data6),30),
                    (sf.Pcnt20(data7),20),
                    (data5,0),
                    (data8,0)
                    
                )
            )
        )
        
    dataL=sf_do(mix1)
    dataR=sf_do(mix2)
    return (dataL,dataR)

dataL1,dataR1=invasion(0.000025,0.000015,45)
dataL2,dataR2=invasion(0.000020,0.000007,45)
dataL3,dataR3=invasion(0.000011,0.000010,45)
dataL4,dataR4=invasion(0.000010,0.000012,45)
dataL=sf.Normalise(
    sf.MixAt(
        (dataL1, 0),
        (dataL2, 30000),
        (dataL3, 60000),
        (dataL1, 90000),
        (dataL4,120000),
        (dataL1,150000),
        (dataL4,160000)
    )
)

dataR=sf.Normalise(
    sf.MixAt(
        (dataR1,     0),
        (dataR2, 30000),
        (dataR3, 60000),
        (dataR1, 90000),
        (dataR4,120000),
        (dataR1,150000),
        (dataR4,160000)
    )
)
sf.WriteFile32((dataL,dataR),"temp/temp.wav")
dataL=0
dataR=0

def reverbInner(signal,convol,grainLength):
    def reverbInnerDo():
        mag=sf.Magnitude(signal)
        if mag>0:
            signal_=sf.Concatenate(signal,sf.Silence(grainLength))
            signal_=sf.FrequencyDomain(signal_)
            signal_=sf.CrossMultiply(convol,signal_)
            signal_=sf.TimeDomain(signal_)
            newMag=sf.Magnitude(signal_)
            signal_=sf.NumericVolume(signal_,mag/newMag)        
            # tail out clicks due to amplitude at end of signal 
            l=sf.Length(signal_)
            sf.Multiply(
                sf.NumericShape(
                    (0,1),
                    (l-100,1),
                    (1,0)
                ),
                signal_
            )
            return signal_
        else:
            return signal
            
    return sf_do(reverbInnerDo)

def reverberate(signal,convol):
    def reverberateDo():
        grainLength = sf.Length(convol)
        convol_=sf.FrequencyDomain(sf.Concatenate(convol,sf.Silence(grainLength)))
        signal_=sf.Concatenate(signal,sf.Silence(grainLength))
        out=[]
        for grain in sf.Granulate(signal_,grainLength):
            (signal_,at)=grain
            out.append((reverbInner(signal_,convol_,grainLength),at))
        return sf.Normalise(sf.MixAt(out))
    return sf_do(reverberateDo)
 
(left,right)=sf.ReadFile("temp/temp.wav")

(convoll,convolr)=sf.ReadFile("temp/revb.wav")
wleft =reverberate(left,convoll)
wright=reverberate(right,convolr)

left=sf.Normalise(sf.MixAt(
    (sf.Pcnt40(wleft),10),
    (sf.Pcnt5(wright),40),
    (sf.Pcnt5(wleft),120),
    (sf.Pcnt45(left),0),
    (sf.Pcnt5(right),110)
))

right=sf.Normalise(sf.MixAt(
    (sf.Pcnt40(wright),10),
    (sf.Pcnt5(wleft),40),
    (sf.Pcnt5(wright),130),
    (sf.Pcnt45(right),0),
    (sf.Pcnt5(left),105)
))

sf.WriteFile32((left,right),"temp/temp_post.wav")