Wednesday, 7 May 2014

The Code Behind The Case

Here is a simple dump of the code which produced 'The Chase'


import random
import math
#sf.SetSampleRate(192000)

# Single threaded for debug
#def sf_do(toDo):
#   return toDo()

def granularReverb(signal,ratio,delay,density):

    def granularReverbInner():
        out=[]
        for grain in sf.Granulate(signal,50,10):
            (signal_i,at)=grain
            signal_i=sf.DirectRelength(signal_i,ratio-0.01+(0.02*random.random()))
            for x in range(0,density):
                out.append(
                    (
                        +signal_i,
                        at + (random.random()+random.random())*delay
                    )
                )
            -signal_i
        return sf.Clean(sf.Normalise(sf.MixAt(out)))
    return sf_do(granularReverbInner)


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 
            return signal_
        else:
            -convol
            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_i,at)=grain
            out.append((reverbInner(signal_i,+convol_,grainLength),at))
        -convol_
        return sf.Realise(sf.Normalise(sf.MixAt(out)))
    return sf_do(reverberateDo)

# Get IRs
violinIRs = sf.ViolinBodyIRs(())
#violaIRs  = sf.ViolaBodyIRs(())
celloIRs  = sf.CelloBodyIRs(())
bassIRs   = sf.BassBodyIRs(())

def createSawTooth(length,pitch):
    signals=[]
    v=1
    opitch=pitch
    it=1.0
    phase=random.random()
    while pitch<18000:
        signals.append(sf.NumericVolume(sf.PhasedSineWave(length,pitch,phase),1.0/it))
        pitch+=opitch
        it+=1
    sig=sf.Mix(signals)
    sig=sf.Realise(sig)
    sig=sf.FixSize(sig)
    return sf.Clean(sig)

def rawString(length,pitch):
    def rawStringA(l,p):
        def rawStringAIn():
            return createSawTooth(l,p)
        return sf_do(rawStringAIn)
    pitch=float(pitch)
    #pitch=pitch+pitch*random.random()*0.0001
    s1=rawStringA(length,pitch)
    s2=rawStringA(length,pitch*2.0)
    s3=rawStringA(length,pitch*4.0)
    s4=sf.WhiteNoise(length)
    signal=sf.Normalise(
        sf.Mix(
            sf.Pcnt20(s4),
            sf.Pcnt50(s1),
            sf.Pcnt20(s2),
            sf.Pcnt10(s3)
        )
    )
    signal=sf.Clean(sf.ResonantFilter(signal,0.95,0.15,sf.Period(pitch)))
    multi=sf.Normalise(
        sf.DirectRelength(
            sf.ButterworthLowPass(sf.WhiteNoise(length/500.0),2500,6),
            0.001
        )
    )
    multi=sf.Cut(0,sf.Length(+signal),multi)
    signal=sf.Resample(
        sf.DirectMix(1,sf.NumericVolume(multi,0.001)),
        signal
    )
    return sf.Realise(sf.Normalise(sf.Clean(signal)))


def playStringClean(a,length,pitch,whiteAmount):
    def rsd():
        return rawString(length,pitch)
    
    signal=0
    if(pitch>500):
        signal=sf.Normalise(sf.Mix(sf_do(rsd),sf_do(rsd),sf_do(rsd)))
    else:
        signal=sf.Normalise(sf.Mix(sf_do(rsd),sf_do(rsd)))
    if(pitch>440):    
        signal=sf.ButterworthHighPass(signal,pitch*0.5,6)
        signal=sf.ButterworthHighPass(signal,2000,1)
        signal=sf.ButterworthLowPass(signal,8000,1)
    if(pitch<128):
        signal=sf.ButterworthHighPass(signal,pitch*0.5,1)
        signal=sf.ButterworthHighPass(signal,500,1)
        signal=sf.ButterworthLowPass(signal,2000,1)
    else:
        signal=sf.ButterworthHighPass(signal,pitch*0.5,3)
        signal=sf.ButterworthHighPass(signal,1500,1)
        signal=sf.ButterworthLowPass(signal,4000,1)

    signal=sf.ButterworthLowPass(signal,pitch*10.0,1)
    signal=sf.Mix(
        sf.Pcnt25(+signal),
        sf.Pcnt75(sf.RBJNotch(signal,pitch,0.5))
    )    
    white=sf.WhiteNoise(length)
    white=sf.ButterworthHighPass(white,pitch*2.0,2)
    white=sf.ButterworthLowPass(white,pitch*6.0,1)
    white=sf.Normalise(white)
    white=sf.Multiply(white,+signal)
    white=sf.NumericVolume(white,whiteAmount)
    signal=sf.NumericVolume(signal,1.0-whiteAmount)
    signal=sf.Normalise(sf.Mix(signal,white))

    sq=sf.Mix(
        sf.PhasedSineWave(length,pitch*0.95,random.random()),
        sf.PhasedSineWave(length,pitch*1.05,random.random())
    )
    envb=sf.NumericShape((0,0.25),(a,0),(length,0))
    sq=sf.Multiply(envb,sf.FixSize(sq))

    enva=sf.NumericShape((0,0.75),(a,1),(length,1))
    signal=sf.Multiply(enva,signal)

    signal=sf.Mix(sq,sf.FixSize(signal))
    sigs=[]
    bodies=[]
    if(pitch<128):
        bodies=bassIRs
    elif(pitch<440):
        bodies=celloIRs
    else:
        bodies=violinIRs
    
    for body in violinIRs:
        sigs.append(reverberate(+signal,+body))  
    -signal
    signal=sf.Normalise(sf.Mix(sigs))
    return signal

def playString(pitch,e,a,d,dat,s,sat,r,whiteAmount,vibStart,vibMiddle,vibAmount,vibRate=2.0):
    print (
        "Performing Note: ",pitch,
        " E:",  e,
        " A:",  a,
        " D:",  d,
        " Dat:",dat,
        " S:",  s,
        " Sat:",sat,
        " R:",  r,
        " WA:", whiteAmount,
        " VS:", vibStart,
        " VM:", vibMiddle,
        " VA:", vibAmount,
        " VR:", vibRate
    )
    def playStringInner():
        envA=sf.SimpleShape(
            (0,-60),
            (e,0),
            (d,0),
            (s,0),
            (r,0)
        )
        envB=sf.NumericShape(
            (0,0),
            (a,1),
            (d,dat),
            (s,sat),
            (r,0)
        )
        env=sf.Multiply(envA,envB)
        sigs=[]
        for x in range(0,5):
            sigs.append(playStringClean(a,r,pitch,whiteAmount))
        signal=sf.Normalise(sf.Mix(sigs))
        signal=sf.Multiply(signal,env)
        if(vibAmount>0):
            l=sf.Length(+signal)
            env=sf.NumericShape((0,0),(vibStart,0),(vibMiddle,1),(r,0.75),(l,0))
            env=sf.NumericVolume(env,vibAmount)
            trem=sf.SineWave(l,2.0+random.random())
            trem=sf.Multiply(env,trem)
            vib=+trem
            trem=sf.DirectMix(1,sf.Pcnt50(trem))
            signal=sf.Multiply(trem,signal)
            vib=sf.DirectMix(1,sf.NumericVolume(vib,0.01))
            signal=sf.Resample(vib,signal)
        
        if(pitch>128):
            signal=sf.ButterworthHighPass(signal,pitch*0.75,3)
            signal=sf.BesselLowPass(signal,pitch,1)
        else:
            signal=sf.ButterworthHighPass(signal,pitch*0.75,3)
            
        return sf.Realise(sf.Normalise(sf.Clean(signal)))
    return sf_do(playStringInner)

def playStringInitial(pitch,length,volume):
    def playStringInitialInner():
        sig=playString(
            pitch,
            64,                 # e
            128,                # a
            length*0.5,         # d
            0.75,               # d at
            length*0.75,        # s
            1.0,                # s at
            length,             # r
            0.75,               # white amount  
            length*0.75,        # vib start
            length*0.75,        # vib middle
            0.5                 # vib amount
        )
        envH=sf.NumericShape((0,1),(length*0.25,0.25),(length,0.25))
        envL=sf.NumericShape((0,0),(length*0.25,0.75),(length,0.75))
        sig=sig.get()
        sigLow=sf.ButterworthLowPass(+sig,pitch*2.0,2)
        sigHi =sf.ButterworthHighPass(sig ,pitch*2.0,2)
        sigLow=sf.Multiply(sigLow,envL)
        sigHi =sf.Multiply(sigHi ,envH)
        env   =sf.NumericShape((0,0),(length*0.25,0.25),(length*0.5,1),(length,1))
        sig   =sf.Mix(sigLow,sigHi)
        sig   =sf.FixSize(sf.Multiply(env,sig))
        return sf.Clean(sf.NumericVolume(sig,volume))
    return sf_do(playStringInitialInner)
    
def playStringSuperSoft(pitch,length,volume):
    def playStringSoftSuperInner():
        if(pitch<256):
            w=0.75
        else:
            if(pitch<720):
                w=0.33
            else:
                w=0.25
        sig = sf.NumericVolume(
            playString(
                pitch,
                64,                 # e
                length*0.25,        # a
                length*0.50,        # d
                1.0,                # d at
                length*0.75,        # s
                1.0,                # s at
                length,             # r
                w,                  # white amount  
                length*0.50,        # vib start
                length*0.75,        # vib middle
                0.5                 # vib amount
            ),
            volume
        )
        env   = sf.NumericShape((0,0),(length*0.25,1),(length,1))
        return sf.Clean(sf.FixSize(sf.Multiply(env,sig)))
    return sf_do(playStringSoftSuperInner)

def playStringSoftShort(pitch,length,volume):
    def playStringSoftShortInner():
        if(pitch<256):
            w=0.5
        else:
            if(pitch<720):
                w=0.25
            else:
                w=0.15
        return sf.Clean(sf.NumericVolume(
            playString(
                pitch,
                32,                 # e
                64,                 # a
                length*0.15,        # d
                1.0,                # d at
                length*0.5,         # s
                0.5,                # s at
                length,             # r
                w,                  # white amount  
                length*0.50,        # vib start
                length*0.75,        # vib middle
                0.5                 # vib amount
            ),
            volume
        ))
    return sf_do(playStringSoftShortInner)

def playStringHardLong(pitch,length,volume):
    def playStringHardLong():
        if(pitch<256):
            w=0.1
        else:
            if(pitch<720):
                w=0.1
            else:
                w=0.05
        return sf.Clean(sf.NumericVolume(
            playString(
                pitch,
                32,                 # e
                64,                 # a
                length*0.25,        # d
                1.0,                # d at
                length*0.75,        # s
                0.75,               # s at
                length,             # r
                w,                  # white amount  
                length*0.25,        # vib start
                length*0.75,        # vib middle
                0.5                 # vib amount - no vib in this case
            ),
            volume
        ))
    return sf_do(playStringHardLong)

def playStringHardShort(pitch,length,volume):
    def playStringHardShortInner():
        if(pitch<256):
            w=0.1
        else:
            if(pitch<720):
                w=0.1
            else:
                w=0.05
        return sf.Clean(sf.NumericVolume(
            playString(
                pitch,
                32,                 # e
                64,                 # a
                length*0.125,       # d
                1.0,                # d at
                length*0.75,         # s
                0.75,               # s at
                length,             # r
                w,                  # white amount  
                length*0.25,        # vib start
                length*0.75,        # vib middle
                0                   # vib amount - no vib in this case
            ),
            volume
        ))
    return sf_do(playStringHardShortInner)

def playStringPluck(pitch,length,volume):
    def playStringPluckInner():
        sig=playString(
                pitch,
                8,                  # e
                16,                 # a
                32,                 # d
                0.5,                # d at
                length*0.75,        # s
                0.25,               # s at
                length,             # r
                0,                  # white amount  
                length*0.50,        # vib start
                length*0.90,        # vib middle
                1                   # vib amount - no vib in this case
            )
        envH=sf.NumericShape((0,0),(32,1),(length,0))
        envL=sf.NumericShape((0,1),(32,0),(length,1))
        sig=sig.get()
        sigL=sf.ButterworthLowPass(+sig,pitch,1)
        sigL=sf.ButterworthLowPass(sigL,pitch*3,1)
        sigH=sf.Multiply(sig,envH)
        sigL=sf.Multiply(sigL,envL)
        sig=sf.Mix(sigL,sigH)
        sig=sf.NumericVolume(sig,volume)
        return sig
        
    return sf_do(playStringPluckInner)

def makeClick(formant):
    clickA=sf.Multiply(
        sf.SimpleShape((0,0),(500,-30),(512,-60)),
        sf.NumericShape((0,0),(100,1),(-300,-1),(400,0)),
        sf.FixSize(sf.Power(sf.WhiteNoise(512),8))
    )
    clickA=sf.BesselLowPass(clickA,440,1)
    clickA=formant(clickA)

    for i in range(0,4):
        clickB=sf.BesselLowPass(+clickA,440,1)
        clickB=formant(clickB)
        clickA=sf.MixAt(
            (clickA,0),
            (clickB,beat/16)
        )
    return sf.Normalise(clickA)

def doFormant(sig,f1,f2,f3):
    def doFormantInner(a,b,c,d):
        def doFII():
            return sf.RBJPeaking(a,b,c,d)
        return sf_do(doFII)
    sig1=doFormantInner(+sig,f1,1,40)
    sig2=doFormantInner(+sig,f2,2,20)
    sig3=doFormantInner( sig,f3,1,40)
    x=sf.Mix(sig1,sig2,sig3)
    x=sf.Normalise(x)
    return sf.Realise(x)

#beat
def sayBeat(sig):
    return doFormant(sig,300,2800,3300)

#bit
def sayBit(sig):
    return doFormant(sig,430,2500,3100)

#bet
def sayBet(sig):
    return doFormant(sig,600,2350,3000)

#bat
def sayBat(sig):
    return doFormant(sig,860,2050,2850)

#part
def sayPart(sig):
    return doFormant(sig,850,1200,2800)

#pot 
def sayPot(sig):
    return doFormant(sig,590,900,2700)

#boat
def sayBoat(sig):
    return doFormant(sig,470,1150,2700)

#book
def sayBook(sig):
    return doFormant(sig,370,950,2650)

#but
def sayBut(sig):
    return doFormant(sig,760,1400,2800)

#pert
def sayPert(sig):
    return doFormant(sig,500,1650,1950)

# oddness
def sayStrange(sig):
    return doFormant(sig,550,1800,2050)

# Very slow indeed
beat = 1024

def phrase1():
    print "Phrase 1"
    at   = 0
    sigl = []
    sigr = []

    pitch=sf.Note("D3")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft  = granularReverb(sigl,0.5,beat*2,512)
    wright = granularReverb(sigr,0.5,beat*2,512)
    wleft  = sf.FixSize(wleft)
    wright = sf.FixSize(wright)
    left  = sf.FixSize(sayPot(wleft))
    right = sf.FixSize(sayPot(wright))
            
    sf.WriteFile32([left,right],"temp/phrase-1.wav")

def phrase2():
    print "Phrase 2"
    at   = 0
    sigl = []
    sigr = []

    pitch=sf.Note("D3")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft1  = sf.Pcnt15(granularReverb(+sigl,0.5 ,beat*2,128))
    wright1 = sf.Pcnt35(granularReverb(+sigr,0.75,beat*2,128))
    wleft2  = sf.Pcnt43(granularReverb(+sigl,0.75,beat*2,128))
    wright2 = sf.Pcnt23(granularReverb(+sigr,0.5 ,beat*2,128))
    wleft3  =           granularReverb( sigl,0.125,beat*2,128)
    wright3 =           granularReverb( sigr,0.125,beat*2,128)
    left  = sf.FixSize(sayBoat(sf.Mix(wleft1,wleft2,wleft3)))
    right = sf.FixSize(sayBoat(sf.Mix(wright1,wright2,wright3)))
        
    sf.WriteFile32([left,right],"temp/phrase-2.wav")


def phrase3():
    print "Phrase 3"
    at   = 0
    sigl = []
    sigr = []

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    sigl.append((playStringPluck(pitch,length,0.5),at))
    sigr.append((playStringPluck(pitch,length,0.5),at))
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    sigl.append((playStringPluck(pitch,length,0.5),at))
    sigr.append((playStringPluck(pitch,length,0.5),at))
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("D4")
    sigl.append((playStringPluck(pitch,length,0.5),at))
    sigr.append((playStringPluck(pitch,length,0.5),at))

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    sigl.append((playStringPluck(pitch,length,0.33),at))
    sigr.append((playStringPluck(pitch,length,0.33),at))

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    sigl.append((playStringPluck(pitch,length,0.5),at))
    sigr.append((playStringPluck(pitch,length,0.5),at))

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    sigl.append((playStringPluck(pitch,length,0.33),at))
    sigr.append((playStringPluck(pitch,length,0.33),at))

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    sigl.append((playStringPluck(pitch,length,0.5),at))
    sigr.append((playStringPluck(pitch,length,0.5),at))
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    sigl.append((playStringPluck(pitch,length,0.25),at))
    sigr.append((playStringPluck(pitch,length,0.25),at))
       
    print "Compiling Phrase3"
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 1.0,beat*0.4,250),
        granularReverb(+left, 0.5,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.0,beat*0.4,250),
        granularReverb(+right,0.5,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt20(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt20(+wright),+right)
    left  = sf.Mix(sf.Pcnt50(wleft) ,sf.Pcnt50(left))
    right = sf.Mix(sf.Pcnt50(wright),sf.Pcnt50(right))
    left = sf.FixSize(left)
    right= sf.FixSize(right)

    sf.WriteFile32([+left,+right],"temp/phrase-3.wav")

    print "Compiling Phrase4"
    
    pitch=sf.Note("D4")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft  = granularReverb(sigl,0.5,beat,128)
    wright = granularReverb(sigr,0.5,beat,128)
    wleft  = sayPart(wleft)
    wright = sayPart(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt35(wleft),0),
        (sf.Pcnt65(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt45(wright),0),
        (sf.Pcnt55(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-4.wav")

def phrase5():
    print "Phrase 5"
    at   = 0
    sigl = []
    sigr = []

    def double():
        sigl.append((playStringHardShort(pitch,length,0.5),at))
        sigr.append((playStringHardShort(pitch,length,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigl.append((playStringHardShort(pitch*2.01,length,0.5),at))
        sigr.append((playStringHardShort(pitch*1.99,length,0.5),at))
        sigl.append((playStringHardShort(pitch*1.99,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch*2.01,length,0.5),at+0.25*beat))    

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    double()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    double()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 1.0,beat*0.4,250),
        granularReverb(+left, 0.5,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.0,beat*0.4,250),
        granularReverb(+right,0.5,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt20(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt20(+wright),+right)

    print "Compiling Phrase5"
    
    pitch=sf.Note("G4")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBoat(wleft)
    wright = sayBoat(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt35(wleft),0),
        (sf.Pcnt65(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt45(wright),0),
        (sf.Pcnt55(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-5.wav")

def phrase6():
    print "Phrase 6"
    at   = 0
    sigl = []
    sigr = []

    def double():
        sigl.append((playStringHardShort(pitch,length,0.5),at))
        sigr.append((playStringHardShort(pitch,length,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigl.append((playStringHardShort(pitch*2.01,length,0.5),at))
        sigr.append((playStringHardShort(pitch*1.99,length,0.5),at))
        sigl.append((playStringHardShort(pitch*1.99,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch*2.01,length,0.5),at+0.25*beat))    

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    double()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    double()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 2.0,beat*0.2,250),
        granularReverb(+left, 1.0,beat*0.4,250),
        granularReverb(+left, 0.5,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.0,beat*0.2,250),
        granularReverb(+right,2.0,beat*0.4,250),
        granularReverb(+right,0.5,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt20(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt20(+wright),+right)

    print "Compiling Phrase6"
    
    pitch=sf.Note("G4")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayPart(wleft)
    wright = sayPart(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt35(wleft),0),
        (sf.Pcnt65(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt45(wright),0),
        (sf.Pcnt55(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-6.wav")

def phrase7():
    print "Phrase 7"
    at   = 0
    sigl = []
    sigr = []

    def double():
        sigl.append((playStringHardShort(pitch*0.5,length*2.0,0.5),at))
        sigr.append((playStringHardShort(pitch*0.5,length*2.0,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at))
        sigr.append((playStringHardShort(pitch,length,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigl.append((playStringHardShort(pitch*2.01,length,0.5),at))
        sigr.append((playStringHardShort(pitch*1.99,length,0.5),at))
        sigl.append((playStringHardShort(pitch*1.99,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch*2.01,length,0.5),at+0.25*beat))    

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    double()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    double()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 2.0,beat*0.2,250),
        granularReverb(+left, 1.0,beat*0.4,250),
        granularReverb(+left, 0.5,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.0,beat*0.2,250),
        granularReverb(+right,2.0,beat*0.4,250),
        granularReverb(+right,0.5,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt20(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt20(+wright),+right)

    print "Compiling Phrase7"
    
    pitch=sf.Note("G4")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBit(wleft)
    wright = sayBit(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt35(wleft),0),
        (sf.Pcnt65(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt45(wright),0),
        (sf.Pcnt55(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-7.wav")

def phrase8():
    print "Phrase 8"
    at   = 0
    sigl = []
    sigr = []

    def double():
        sigl.append((playStringHardLong(pitch*0.25,length*2.0,0.5),at))
        sigr.append((playStringHardLong(pitch*0.25,length*2.0,0.5),at))
        sigl.append((playStringHardShort(pitch*0.5,length*2.0,0.5),at))
        sigr.append((playStringHardShort(pitch*0.5,length*2.0,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at))
        sigr.append((playStringHardShort(pitch,length,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigl.append((playStringPluck(pitch*2.01,length,0.5),at))
        sigr.append((playStringPluck(pitch*1.99,length,0.5),at))
        sigl.append((playStringPluck(pitch*1.99,length,0.5),at+0.25*beat))
        sigr.append((playStringPluck(pitch*2.01,length,0.5),at+0.25*beat))    

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    double()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    double()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 2.0,beat*0.2,250),
        granularReverb(+left, 1.0,beat*0.4,250),
        granularReverb(+left, 0.5,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.0,beat*0.2,250),
        granularReverb(+right,2.0,beat*0.4,250),
        granularReverb(+right,0.5,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt40(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt40(+wright),+right)

    print "Compiling Phrase8"
    
    pitch=sf.Note("G4")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBoat(wleft)
    wright = sayBoat(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt35(wleft),0),
        (sf.Pcnt65(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt45(wright),0),
        (sf.Pcnt55(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-8.wav")

def phrase9():
    print "Phrase 9"
    at   = 0
    sigl = []
    sigr = []

    def double():
        sigl.append((playStringHardLong(pitch*0.25,length*2.0,0.5),at))
        sigr.append((playStringHardLong(pitch*0.25,length*2.0,0.5),at))
        sigl.append((playStringHardShort(pitch*0.5,length*2.0,0.5),at))
        sigr.append((playStringHardShort(pitch*0.5,length*2.0,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at))
        sigr.append((playStringHardShort(pitch,length,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigl.append((playStringPluck(pitch*2.01,length,0.5),at))
        sigr.append((playStringPluck(pitch*1.99,length,0.5),at))
        sigl.append((playStringPluck(pitch*1.99,length,0.5),at+0.25*beat))
        sigr.append((playStringPluck(pitch*2.01,length,0.5),at+0.25*beat))    

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("E4")
    double()

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    double()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    double()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("G3#")
    double()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 2.0,beat*0.2,250),
        granularReverb(+left, 1.0,beat*0.4,250),
        granularReverb(+left, 0.5,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.0,beat*0.2,250),
        granularReverb(+right,2.0,beat*0.4,250),
        granularReverb(+right,0.5,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt40(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt40(+wright),+right)

    print "Compiling Phrase8"
    
    pitch=sf.Note("F4")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBook(wleft)
    wright = sayBook(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt35(wleft),0),
        (sf.Pcnt65(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt45(wright),0),
        (sf.Pcnt55(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-9.wav")
    
def phrase10():
    print "Phrase 10"
    at   = 0
    sigl = []
    sigr = []

    def double():
        sigl.append((playStringHardLong(pitch*0.25,length*2.0,0.5),at))
        sigr.append((playStringHardLong(pitch*0.25,length*2.0,0.5),at))
        sigl.append((playStringHardShort(pitch*0.5,length*2.0,0.5),at))
        sigr.append((playStringHardShort(pitch*0.5,length*2.0,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at))
        sigr.append((playStringHardShort(pitch,length,0.5),at))
        sigl.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(pitch,length,0.5),at+0.25*beat))
        sigl.append((playStringPluck(pitch*2.01,length,0.5),at))
        sigr.append((playStringPluck(pitch*1.99,length,0.5),at))
        sigl.append((playStringPluck(pitch*1.99,length,0.5),at+0.25*beat))
        sigr.append((playStringPluck(pitch*2.01,length,0.5),at+0.25*beat))    

    def vipvip():
        sigl.append((playStringPluck(pitch,length,0.5),at))
        sigr.append((playStringPluck(pitch,length,0.5),at))
        

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    pitch=sf.Note("G6")
    vipvip()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("E4")
    double()

    pitch=sf.Note("A6")
    vipvip()

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    pitch=sf.Note("G6")
    vipvip()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    double()

    pitch=sf.Note("F6")
    vipvip()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("G3#")
    double()

    pitch=sf.Note("F6#")
    vipvip()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 2.0,beat*0.2,250),
        granularReverb(+left, 1.0,beat*0.4,250),
        granularReverb(+left, 0.5,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.0,beat*0.2,250),
        granularReverb(+right,2.0,beat*0.4,250),
        granularReverb(+right,0.5,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt40(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt40(+wright),+right)

    print "Compiling Phrase10"
    
    pitch=sf.Note("A4")
    length=beat*2.0
    sigl=playStringSuperSoft(pitch,length,0.5)
    sigr=playStringSuperSoft(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBit(wleft)
    wright = sayBit(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt35(wleft),0),
        (sf.Pcnt65(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt45(wright),0),
        (sf.Pcnt55(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-10.wav")
 
def phrase11():
    print "Phrase 11"
    at   = 0
    sigl = []
    sigr = []

    # pitch up minor third
    def double():
        lpitch=pitch*sf.Semitone(())**3.0
        sigl.append((playStringHardLong(lpitch*0.25,length*2.0,0.5),at))
        sigr.append((playStringHardLong(lpitch*0.25,length*2.0,0.5),at))
        sigl.append((playStringHardShort(lpitch*0.5,length*2.0,0.5),at))
        sigr.append((playStringHardShort(lpitch*0.5,length*2.0,0.5),at))
        sigl.append((playStringHardShort(lpitch,length,0.5),at))
        sigr.append((playStringHardShort(lpitch,length,0.5),at))
        sigl.append((playStringHardShort(lpitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(lpitch,length,0.5),at+0.25*beat))
        sigl.append((playStringPluck(lpitch*2.01,length,0.5),at))
        sigr.append((playStringPluck(lpitch*1.99,length,0.5),at))
        sigl.append((playStringPluck(lpitch*1.99,length,0.5),at+0.25*beat))
        sigr.append((playStringPluck(lpitch*2.01,length,0.5),at+0.25*beat))    

    def vipvip():
        lpitch=pitch*sf.Semitone(())**3.0
        sigl.append((playStringPluck(lpitch,length,0.5),at))
        sigr.append((playStringPluck(lpitch,length,0.5),at))
        

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    pitch=sf.Note("G6")
    vipvip()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("E4")
    double()

    pitch=sf.Note("A6")
    vipvip()

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    pitch=sf.Note("G6")
    vipvip()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    double()

    pitch=sf.Note("F6")
    vipvip()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("G3#")
    double()

    pitch=sf.Note("F6#")
    vipvip()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 2.0,beat*0.2,250),
        granularReverb(+left, 1.0,beat*0.4,250),
        granularReverb(+left, 0.5,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.0,beat*0.2,250),
        granularReverb(+right,2.0,beat*0.4,250),
        granularReverb(+right,0.5,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt40(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt40(+wright),+right)

    print "Compiling Phrase10"
    
    pitch=sf.Note("A3")
    length=beat*2.0
    sigl=playStringHardLong(pitch,length,0.5)
    sigr=playStringHardLong(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBat(wleft)
    wright = sayBat(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt35(wleft),0),
        (sf.Pcnt65(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt45(wright),0),
        (sf.Pcnt55(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-11.wav") 

def phrase12():
    print "Phrase 12"
    at   = 0
    sigl = []
    sigr = []

    # pitch up a fifth
    def double():
        lpitch=pitch*sf.Semitone(())**7.0
        sigl.append((playStringHardLong(lpitch*0.25,length*2.0,0.75),at))
        sigr.append((playStringHardLong(lpitch*0.25,length*2.0,0.25),at))
        sigl.append((playStringHardShort(lpitch*0.5,length*2.0,0.75),at))
        sigr.append((playStringHardShort(lpitch*0.5,length*2.0,0.25),at))
        sigl.append((playStringHardShort(lpitch,length,0.5),at))
        sigr.append((playStringHardShort(lpitch,length,0.5),at))
        sigl.append((playStringHardShort(lpitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(lpitch,length,0.5),at+0.25*beat))
        sigl.append((playStringPluck(lpitch*2.01,length,0.6),at))
        sigr.append((playStringPluck(lpitch*1.99,length,0.4),at))
        sigl.append((playStringPluck(lpitch*1.99,length,0.6),at+0.25*beat))
        sigr.append((playStringPluck(lpitch*2.01,length,0.4),at+0.25*beat))    

    def vipvip():
        lpitch=pitch*sf.Semitone(())**7.0
        sigl.append((playStringPluck(lpitch,length,0.25),at))
        sigr.append((playStringPluck(lpitch,length,0.75),at))
        

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    pitch=sf.Note("G6")
    vipvip()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("E4")
    double()

    pitch=sf.Note("A6")
    vipvip()

    2.5
    at+=beat*0.5
    pitch=sf.Note("C4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    pitch=sf.Note("G6")
    vipvip()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("A3")
    double()

    pitch=sf.Note("F6")
    vipvip()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("B3")
    double()

    pitch=sf.Note("F6#")
    vipvip()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 2.00,beat*0.2,250),
        granularReverb(+left, 1.00,beat*0.4,250),
        granularReverb(+left, 0.50,beat*0.8,250),
        granularReverb(+left, 0.25,beat*0.8,250)
    )        
    wright = sf.Mix(
        granularReverb(+right,1.00,beat*0.2,250),
        granularReverb(+right,2.00,beat*0.4,250),
        granularReverb(+right,0.50,beat*0.8,250),
        granularReverb(+right,0.25,beat*0.8,250)
    )

    left2  = sf.Mix(sf.Pcnt40(+wleft), +left)
    right2 = sf.Mix(sf.Pcnt40(+wright),+right)

    print "Compiling Phrase11"
    
    pitch=sf.Note("A3")
    length=beat*2.0
    sigl=playStringHardLong(pitch,length,0.5)
    sigr=playStringHardLong(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBeat(wleft)
    wright = sayBeat(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt50(wleft),0),
        (sf.Pcnt50(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt50(wright),0),
        (sf.Pcnt50(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-12.wav") 

def phrase14():
    print "Phrase 14"
    at   = 0
    sigl = []
    sigr = []

    # pitch up a fifth
    def double():
        lpitch=pitch*sf.Semitone(())**7.0
        sigl.append((playStringHardLong(lpitch*0.25,length*2.0,0.75),at))
        sigr.append((playStringHardLong(lpitch*0.25,length*2.0,0.25),at))
        sigl.append((playStringHardShort(lpitch*0.5,length*2.0,0.75),at))
        sigr.append((playStringHardShort(lpitch*0.5,length*2.0,0.25),at))
        sigl.append((playStringHardShort(lpitch,length,0.5),at))
        sigr.append((playStringHardShort(lpitch,length,0.5),at))
        sigl.append((playStringHardShort(lpitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(lpitch,length,0.5),at+0.25*beat))
        sigl.append((playStringPluck(lpitch*2.01,length,0.6),at))
        sigr.append((playStringPluck(lpitch*1.99,length,0.4),at))
        sigl.append((playStringPluck(lpitch*1.99,length,0.6),at+0.25*beat))
        sigr.append((playStringPluck(lpitch*2.01,length,0.4),at+0.25*beat))    

    def vipvip():
        lpitch=pitch*sf.Semitone(())**7.0
        sigl.append((playStringPluck(lpitch,length,0.25),at))
        sigr.append((playStringPluck(lpitch,length,0.75),at))
        

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    pitch=sf.Note("G6")
    vipvip()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("E4")
    double()

    pitch=sf.Note("A6")
    vipvip()

    2.5
    at+=beat*0.5
    pitch=sf.Note("F4")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    pitch=sf.Note("G6")
    vipvip()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("D4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("E4")
    double()

    pitch=sf.Note("F6")
    vipvip()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("F4")
    double()

    pitch=sf.Note("F6#")
    vipvip()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 4.00 ,beat*0.2,250),
        granularReverb(+left, 2.00 ,beat*0.4,250),
        granularReverb(+left, 0.25 ,beat*0.8,250),
        granularReverb(+left, 0.125,beat*0.8,512)
    )        
    wright = sf.Mix(
        granularReverb(+right,4.00 ,beat*0.2,250),
        granularReverb(+right,2.00 ,beat*0.4,250),
        granularReverb(+right,0.25 ,beat*0.8,250),
        granularReverb(+right,0.125,beat*0.8,512)
    )

    left2  = sf.Mix(sf.Pcnt50(+wleft), sf.Pcnt50(+left))
    right2 = sf.Mix(sf.Pcnt50(+wright),sf.Pcnt50(+right))

    print "Compiling Phrase14"
    
    pitch=sf.Note("B3")
    length=beat*2.0
    sigl=playStringHardLong(pitch,length,0.5)
    sigr=playStringHardLong(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBit(wleft)
    wright = sayBit(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt50(wleft),0),
        (sf.Pcnt50(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt50(wright),0),
        (sf.Pcnt50(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-14.wav") 

def phrase15():
    print "Phrase 15"
    at   = 0
    sigl = []
    sigr = []

    # pitch up a octave and semitone
    def double():
        lpitch=pitch*sf.Semitone(())**13.0
        sigl.append((playStringHardLong(lpitch*0.25,length*2.0,0.75),at))
        sigr.append((playStringHardLong(lpitch*0.25,length*2.0,0.25),at))
        sigl.append((playStringHardShort(lpitch*0.5,length*2.0,0.75),at))
        sigr.append((playStringHardShort(lpitch*0.5,length*2.0,0.25),at))
        sigl.append((playStringHardShort(lpitch,length,0.5),at))
        sigr.append((playStringHardShort(lpitch,length,0.5),at))
        sigl.append((playStringHardShort(lpitch,length,0.5),at+0.25*beat))
        sigr.append((playStringHardShort(lpitch,length,0.5),at+0.25*beat))
        sigl.append((playStringPluck(lpitch*2.01,length,0.6),at))
        sigr.append((playStringPluck(lpitch*1.99,length,0.4),at))
        sigl.append((playStringPluck(lpitch*1.99,length,0.6),at+0.25*beat))
        sigr.append((playStringPluck(lpitch*2.01,length,0.4),at+0.25*beat))    

    def vipvip():
        lpitch=pitch*sf.Semitone(())**13.0
        sigl.append((playStringPluck(lpitch,length,0.25),at))
        sigr.append((playStringPluck(lpitch,length,0.75),at))
        

    # 1
    pitch=sf.Note("D4")
    length=beat*0.5
    double()
    
    pitch=sf.Note("G6")
    vipvip()
    
    # 1.5
    at+=beat*0.5
    pitch=sf.Note("D4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()
    
    # 2
    at+=beat*0.5
    pitch=sf.Note("E4")
    double()

    pitch=sf.Note("A6")
    vipvip()

    2.5
    at+=beat*0.5
    pitch=sf.Note("F4")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 3
    at+=beat*0.5
    pitch=sf.Note("D4")
    double()

    pitch=sf.Note("G6")
    vipvip()

    # 3.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("D4#")
    double()

    pitch=sf.Note("G6#")
    vipvip()

    # 4
    at+=beat*0.5
    length=beat*0.5
    pitch=sf.Note("E4")
    double()

    pitch=sf.Note("F6")
    vipvip()
    
    # 4.5
    at+=beat*0.5
    length=beat*0.4
    pitch=sf.Note("F4")
    double()

    pitch=sf.Note("F6#")
    vipvip()
       
    left=sf.FixSize(sf.MixAt(sigl))
    right=sf.FixSize(sf.MixAt(sigr))
    wleft  = sf.Mix(
        granularReverb(+left, 4.00 ,beat*0.2,250),
        granularReverb(+left, 2.00 ,beat*0.4,250),
        granularReverb(+left, 0.25 ,beat*0.8,250),
        granularReverb(+left, 0.125,beat*0.8,512)
    )        
    wright = sf.Mix(
        granularReverb(+right,4.00 ,beat*0.2,250),
        granularReverb(+right,2.00 ,beat*0.4,250),
        granularReverb(+right,0.25 ,beat*0.8,250),
        granularReverb(+right,0.125,beat*0.8,512)
    )

    left2  = sf.Mix(sf.Pcnt50(+wleft), sf.Pcnt50(+left))
    right2 = sf.Mix(sf.Pcnt50(+wright),sf.Pcnt50(+right))

    print "Compiling Phrase15"
    
    pitch=sf.Note("B3")
    length=beat*2.0
    sigl=playStringHardLong(pitch,length,0.5)
    sigr=playStringHardLong(pitch,length,0.5)
    wleft  = sf.Mix(
        granularReverb(+sigl,0.50,beat,128),
        granularReverb( sigl,0.75,beat,128)
    )
    wright = sf.Mix(
        granularReverb(+sigr,0.50,beat,128),
        granularReverb( sigr,0.75,beat,128)
    )
    wleft  = sayBoat(wleft)
    wright = sayBoat(wright)
    
    left=sf.Normalise(sf.MixAt(
        (sf.Pcnt50(wleft),0),
        (sf.Pcnt50(left2),0)
    ))
    
    right=sf.Normalise(sf.MixAt(
        (sf.Pcnt50(wright),0),
        (sf.Pcnt50(right2),0)
    ))
        
    sf.WriteFile32([left,right],"temp/phrase-15.wav") 

#phrase1()
#phrase2()
#phrase3()
#phrase4()
#phrase5()
#phrase6()
#phrase7()
#phrase8()
#phrase9()
#phrase10()
#phrase11()
#phrase12()
#phrase14()
#phrase15()

(left1,right1)=sf.ReadFile("temp/phrase-1.wav")
left1  = sf.Swap(left1)
right1 = sf.Swap(right1)

(left2,right2)=sf.ReadFile("temp/phrase-2.wav")
left2  = sf.Swap(left2)
right2 = sf.Swap(right2)


(left3,right3)=sf.ReadFile("temp/phrase-3.wav")
left3  = sf.Swap(left3)
right3 = sf.Swap(right3)

(left4,right4)=sf.ReadFile("temp/phrase-4.wav")
left4  = sf.Swap(left4)
right4 = sf.Swap(right4)

(left5,right5)=sf.ReadFile("temp/phrase-5.wav")
left5  = sf.Swap(left5)
right5 = sf.Swap(right5)

(left6,right6)=sf.ReadFile("temp/phrase-6.wav")
left6  = sf.Swap(left6)
right6 = sf.Swap(right6)

(left7,right7)=sf.ReadFile("temp/phrase-7.wav")
left7  = sf.Swap(left7)
right7 = sf.Swap(right7)

(left8,right8)=sf.ReadFile("temp/phrase-8.wav")
left8  = sf.Swap(left8)
right8 = sf.Swap(right8)

(left9,right9)=sf.ReadFile("temp/phrase-9.wav")
left9  = sf.Swap(left9)
right9 = sf.Swap(right9)

(left10,right10)=sf.ReadFile("temp/phrase-10.wav")
left10  = sf.Swap(left10)
right10 = sf.Swap(right10)

(left11,right11)=sf.ReadFile("temp/phrase-11.wav")
left11  = sf.Swap(left11)
right11 = sf.Swap(right11)

(left12,right12)=sf.ReadFile("temp/phrase-12.wav")
left12  = sf.Swap(left12)
right12 = sf.Swap(right12)

(left14,right14)=sf.ReadFile("temp/phrase-14.wav")
left14  = sf.Swap(left14)
right14 = sf.Swap(right14)

(left15,right15)=sf.ReadFile("temp/phrase-15.wav")
left15  = sf.Swap(left15)
right15 = sf.Swap(right15)

(left16,right16)=sf.ReadFile("temp/phrase-16.wav")
left16  = sf.Swap(left16)
right16 = sf.Swap(right16)

convoll=sf.Multiply(sf.WhiteNoise(beat*4.0),sf.NumericShape((0,0),(beat*0.5,1),(beat,0.1),(beat*4,0)))
convolr=sf.Multiply(sf.WhiteNoise(beat*4.0),sf.NumericShape((0,0),(beat*0.5,1),(beat,0.1),(beat*4,0)))
left13 =reverberate(+left12,convoll)
right13=reverberate(+right12,convolr)


left = sf.FixSize(
    sf.MixAt(
        ( sf.Pcnt30(                  +left1)      ,1000),
       
        ( sf.Pcnt40(                   left2)      ,1000+beat*4),

        ( sf.Pcnt20(sf.BesselHighPass(+left3,32,1)),1000+beat*8),

        ( sf.Pcnt40(sf.BesselHighPass( left4,32,1)),1000+beat*12),

        ( sf.Pcnt50(                   left5)      ,1000+beat*16),

        ( sf.Pcnt50(                   left6)      ,1000+beat*20),

        ( sf.Pcnt50(                   left7)      ,1000+beat*24),

        ( sf.Pcnt50(                  +left8)      ,1000+beat*28),
        ( sf.Pcnt50(                  +left8)      ,1000+beat*32),
        ( sf.Pcnt50(                  +left8)      ,1000+beat*36),

        ( sf.Pcnt50(                  +left9)      ,1000+beat*40),
        ( sf.Pcnt50(                  +left9)      ,1000+beat*44),
        ( sf.Pcnt50(                   left9)      ,1000+beat*48),
        
        ( sf.Pcnt50(                  +left10)     ,1000+beat*52),
        ( sf.Pcnt50(                  +left10)     ,1000+beat*56),
        ( sf.Pcnt50(                  +left10)     ,1000+beat*60),
     
        ( sf.Pcnt50(                  +left11)     ,1000+beat*64),
        ( sf.Pcnt50(                  +left11)     ,1000+beat*68),
        ( sf.Pcnt50(                  +left11)     ,1000+beat*72),
     
        ( sf.Pcnt50(                  +left12)     ,1000+beat*76),
        ( sf.Pcnt50(                  +left12)     ,1000+beat*80),
        ( sf.Pcnt50(                  +left12)     ,1000+beat*84),

        ( sf.Pcnt10(                  +left13)     ,1000+beat*88),
        ( sf.Pcnt10(                   left13)     ,1000+beat*92),

        ( sf.Pcnt20(sf.ButterworthHighPass(+left3,400,2)),1000+beat*96),
        ( sf.Pcnt20(sf.BesselHighPass(      left3,32 ,1)),1000+beat*100),
        
        ( sf.Pcnt50(                  +left8)      ,1000+beat*104),
        ( sf.Pcnt50(                  +left8)      ,1000+beat*108),
        ( sf.Pcnt50(                   left8)      ,1000+beat*112),

        ( sf.Pcnt50(                  +left10)     ,1000+beat*116),
        ( sf.Pcnt50(                  +left10)     ,1000+beat*120),
        ( sf.Pcnt50(                   left10)     ,1000+beat*124),
     
        ( sf.Pcnt50(                  +left11)     ,1000+beat*128),
        ( sf.Pcnt50(                  +left11)     ,1000+beat*132),
        ( sf.Pcnt50(                   left11)     ,1000+beat*136),
     
        ( sf.Pcnt50(                  +left12)     ,1000+beat*140),
        ( sf.Pcnt50(                  +left12)     ,1000+beat*144),
        ( sf.Pcnt50(                   left12)     ,1000+beat*148),

        ( sf.Pcnt50(                  +left14)     ,1000+beat*152),
        ( sf.Pcnt50(                  +left14)     ,1000+beat*156),
        ( sf.Pcnt50(                   left14)     ,1000+beat*160),

        ( sf.Pcnt50(                  +left15)     ,1000+beat*164),
        ( sf.Pcnt50(                  +left15)     ,1000+beat*168),
        ( sf.Pcnt50(                   left15)     ,1000+beat*172),

        ( sf.Pcnt50(                   left16)     ,1000+beat*178)
    )
)

right = sf.FixSize(
    sf.MixAt(
        ( sf.Pcnt20(                  +right1)      ,1000),
        
        ( sf.Pcnt60(                   right2)      ,1000+beat*4),
        
        ( sf.Pcnt30(sf.BesselHighPass(+right3,32,1)),1000+beat*8),
        
        ( sf.Pcnt60(sf.BesselHighPass( right4,32,1)),1000+beat*12),

        ( sf.Pcnt50(                   right5)      ,1000+beat*16),

        ( sf.Pcnt50(                   right6)      ,1000+beat*20),

        ( sf.Pcnt50(                   right7)      ,1000+beat*24),

        ( sf.Pcnt50(                  +right8)      ,1000+beat*28),
        ( sf.Pcnt50(                  +right8)      ,1000+beat*32),
        ( sf.Pcnt50(                  +right8)      ,1000+beat*36),

        ( sf.Pcnt50(                  +right9)      ,1000+beat*40),
        ( sf.Pcnt50(                  +right9)      ,1000+beat*44),
        ( sf.Pcnt50(                   right9)      ,1000+beat*48),
        
        ( sf.Pcnt50(                  +right10)     ,1000+beat*52),
        ( sf.Pcnt50(                  +right10)     ,1000+beat*56),
        ( sf.Pcnt50(                  +right10)     ,1000+beat*60),

        ( sf.Pcnt50(                  +right11)     ,1000+beat*64),
        ( sf.Pcnt50(                  +right11)     ,1000+beat*68),
        ( sf.Pcnt50(                  +right11)     ,1000+beat*72),

        ( sf.Pcnt50(                  +right12)     ,1000+beat*76),
        ( sf.Pcnt50(                  +right12)     ,1000+beat*80),
        ( sf.Pcnt50(                  +right12)     ,1000+beat*84),

        ( sf.Pcnt10(                  +right13)     ,1000+beat*88),
        ( sf.Pcnt10(                   right13)     ,1000+beat*92),
        
        ( sf.Pcnt20(sf.ButterworthHighPass(+right3,400,2)),1000+beat*96),
        ( sf.Pcnt20(sf.BesselHighPass(      right3,32,1)), 1000+beat*100),
        
        ( sf.Pcnt50(                  +right8)      ,1000+beat*104),
        ( sf.Pcnt50(                  +right8)      ,1000+beat*108),
        ( sf.Pcnt50(                   right8)      ,1000+beat*112),      

        ( sf.Pcnt50(                  +right10)     ,1000+beat*116),
        ( sf.Pcnt50(                  +right10)     ,1000+beat*120),
        ( sf.Pcnt50(                   right10)     ,1000+beat*124),

        ( sf.Pcnt50(                  +right11)     ,1000+beat*128),
        ( sf.Pcnt50(                  +right11)     ,1000+beat*132),
        ( sf.Pcnt50(                   right11)     ,1000+beat*136),

        ( sf.Pcnt50(                  +right12)     ,1000+beat*140),
        ( sf.Pcnt50(                  +right12)     ,1000+beat*144),
        ( sf.Pcnt50(                   right12)     ,1000+beat*148),
 
        ( sf.Pcnt50(                  +right14)     ,1000+beat*152),
        ( sf.Pcnt50(                  +right14)     ,1000+beat*156),
        ( sf.Pcnt50(                   right14)     ,1000+beat*160),

        ( sf.Pcnt50(                  +right15)     ,1000+beat*164),
        ( sf.Pcnt50(                  +right15)     ,1000+beat*168),
        ( sf.Pcnt50(                   right15)     ,1000+beat*172),

        ( sf.Pcnt50(                   right16)     ,1000+beat*178)       
    )
)

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

(convoll,convolr)=sf.ReadFile("temp/TheChase-Revb.wav")

wleft =reverberate(+left,convoll)
wright=reverberate(+right,convolr)

left_out=sf.Normalise(sf.MixAt(
    (sf.Pcnt20(wleft),0),
    (sf.Pcnt80(left),00)
))

right_out=sf.Normalise(sf.MixAt(
    (sf.Pcnt20(wright),0),
    (sf.Pcnt80(right),0)
))

sf.WriteFile32((left_out,right_out),"temp/temp_postb.wav")


Thursday, 20 March 2014

Sonic Field On GitHub

Sonic Field Is Now On Github



Journey - Further Experiments 7

It was about time I did some Further Experiments. This one is in high resolution resonance and phase modulation synthesis.

The video of 'Journey'

The theme of Sython Song seems to be random walks and experimentation in generative techniques rather than hard core synthesis; so, despite 'Journey' being created in Python I have grouped it under Further Experiments In Extreme Synthesis. It feels really good to be able to bring this piece to life not because it is somehow amazing but because it was so easy achieved computationally.

Of late I have been trying to create 'Perfect Fi' - in other words, synthesis without artefacts. Clearly that is impossible. However, it makes a good target to aim for. One of the biggest problems for digital synthesis is aliasing producing low frequency inharmonics. These build up until then form a low frequency 'swell' of sound which is something cool be usually horrible.

There are three ways to reduce this effect. 
1) Don't make the aliased frequencies in the first place
2) Reduce them through filtering
3) Reduce them by lifting the Nyquist frequency

Using more additive synthesis helps with 1. Distortion synthesis has a nasty habit of creating aliased frequencies. For example, distorting a sine wave into a sawtooth generates frequencies all the way up to the sample rate; fully half of these will be aliased. Producing the saw tooth by adding frequencies up to the Nyquist limit produces a much cleaner sound.

The 'Clean' decimating filter helps with 2. Being more careful with filtering as one goes along helps two. If a signal is carefully filtered before passing to a distortion will help avoid he distortion injecting aliased frequenceis.

Journey uses 3 for the first time in a long time. It was created at 192 000 samples per second rather than the default 96000. This means that many less frequencies from the audio spectrum end up 'escaping' up above the Nyquist limit because that limit is up at 96KHz.

This has been a challenge until now because 192000 takes twice as much storage as 96000. Storage has been the major performance limit for Sonic Field. I originally designed SF to run in the Cloud. I had this idea that memory would be no limit. However, I soon realised I was unlikely to actually cluster SF in the cloud any time soon. I run it on a 16Gig Mac Book Pro myself. The runs out of memory very easily doing a complex patch. 

But no longer - audio signals can not be backed by Memory Mapped Files. Thus I can run patches which required 30,40,50 or even 100 Gigs quite effectively. The performance drop is too bad if the patch is written to access small blocks of audio at a time. 

Until the latest release of SF the previous data files system and the memory mapped one suffered from no having aggressive garbage collection. I tried to couple them to the Java garbage collector. That did not work well. It resulted in data which was going to be garbage collected still being written to disk. To get the system to work well, I needed to collect file backed garbage straight away at the point it was not longer wanted. 

Now SF has reference counted garbage collection
The implementation under the covers will be published on Nerds Central.
From a patch writing point of view is it simple:
  1. All new signals are generated with a count of 1.
  2. Every time a signal is passed to a processor its count is reduced.
  3. When a count hits 0 the signal is garbage collected.
  4. The + operators (as in +mySignal) will increase the count of a signal.
  5. If the same signal is to be passed to more than one processor the + operator can be used to keep the correct count.
  6. In some situations, this can lead to the count being too high and the signal not being collected.
  7. The - operator reduces the count and so allows collection under these situations.

Here is an example of using the + and - operators:

        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_

This new system makes SF run much faster due to not wasting resource writing dead data out to disk.

Here is the patch which created Journey:

import math
import random
sf.SetSampleRate(192000)

# Single threaded for debug
#def sf_do(toDo):
#   return toDo()

def ring(pitch,length):
    print "Ring: " + str(pitch) + "/" + str(length)
    sig1 = sf.SineWave(length,pitch*1.2)
    sig2 = sf.SineWave(length,pitch*1.2 + 1)
    env  = sf.SimpleShape((0,-60),(125,0),(length,-30))
    
    sig1 = sf.Multiply(+env,sig1)
    sig1 = sf.Pcnt90(sf.DirectMix(1,sig1))
    sig3 = sf.PhaseModulatedSineWave(pitch,sig1)
    sig3 = sf.Multiply(+env,sig3)

    sig2 = sf.Multiply(+env,sig2)
    sig2 = sf.Pcnt90(sf.DirectMix(1,sig2))
    sig4 = sf.PhaseModulatedSineWave(pitch,sig2)
    sig4 = sf.Multiply(env,sig4)
    
    sig5 = sf.Volume(sf.Mix(sig3,sig4),6)
    sig=sf.Saturate(sig5)
    sig=sf.ResonantFilter(sig,0.99,0.05,1000.0/pitch)
    return sf.Realise(sf.Normalise(sig))

def doFormant(sig,f1,f2,f3):
    #sig=sf.BesselLowPass(sig,f3,1)
    def doFormantInner(a,b,c,d):
        def doFII():
            return sf.RBJPeaking(a,b,c,d)
        return sf_do(doFII)
    sig1=doFormantInner(+sig,f1,1,40)
    sig2=doFormantInner(+sig,f2,2,20)
    sig3=doFormantInner( sig,f3,1,40)
    x=sf.Mix(sig1,sig2,sig3)
    x=sf.Normalise(x)
    return sf.Realise(x)

def makeSingBase(pitch,length):
    pitch=float(pitch)
    length=float(length)
    drop=1.0
    notes=[]
    for i in range(1,100):
        thisPitch=pitch*i
        if(thisPitch>10000):
            continue
        print thisPitch
        notes.append(sf.NumericVolume(sf.PhasedSineWave(length,thisPitch,random.random()),drop))
        drop=drop*0.6
    sig=sf.Normalise(sf.Mix(notes))
    return sig

def doSingEnv(sig):
    length=sf.Length(+sig)
    a=0
    d=0
    s=0
    r=length
    k1=50.0
    k2=length-50.0
    if(length<1000):
        a=100.0
        d=250.0
        s=(length-d)/2.0+d
    else:
        a=length*0.1
        d=length*0.25
        s=length*0.5
        
    env=sf.SimpleShape((0,-90),(k1,-30),(a,0),(d,-6),(s,-12),(k2,-30),(r,-90))
    sig=sf.Multiply(sig,env)
    sig=sf.Normalise(sig)
    return sig

#beat
def doFormant1(sig):
    return doFormant(sig,300,2800,3300)

#bit
def doFormant2(sig):
    return doFormant(sig,430,2500,3100)

#bet
def doFormant3(sig):
    return doFormant(sig,600,2350,3000)

#bat
def doFormant4(sig):
    return doFormant(sig,860,2050,2850)

#part
def doFormant5(sig):
    return doFormant(sig,850,1200,2800)

#pot 
def doFormant6(sig):
    return doFormant(sig,590,900,2700)

#boat
def doFormant7(sig):
    return doFormant(sig,470,1150,2700)

#boat
def doFormant8(sig):
    return doFormant(sig,470,1150,2700)

#book
def doFormant9(sig):
    return doFormant(sig,370,950,2650)
#but
def doFormant10(sig):
    return doFormant(sig,760,1400,2800)

#pert
def doFormant11(sig):
    return doFormant(sig,500,1650,1950)

formants=[
    doFormant1,
    doFormant2,
    doFormant3,
    doFormant4,
    doFormant5,
    doFormant6,
    doFormant7,
    doFormant8,
    doFormant9,
    doFormant10,
    doFormant11
]

def doNote(pitch,length,formant):
    def doNoteInner():
        sig=ring(pitch,length)
        sig=formants[int(formant)](sig)
        length_=sf.Length(+sig)
        env=sf.NumericShape((0,0),(length_/2.0,1),(length_,0))
        y=sf.Multiply(sig,env)
        x=sf.Realise(y)
        return x
    return sf_do(doNoteInner)


root    = 32
initial = sf.Silence(2000)
nNotes  = 64
length  = 16384
#length  = 1024

def makeTrack():
    notesL=[]
    notesR=[]
    at=1000
    for x in range(0,nNotes):
        print "Performing note: " + str(x)
        a  = 1+x%7
        b  = 1+x%11
        c  = 2*(1+x%3)
        d  = ((x+1)%3)*2
        e  = math.floor(x%22/2)
        f  = math.floor((11+x)%22/2)
        g  = 1.0+(x%8.0)/3.0
        h  = 1.0+(x%16.0)/6.0
        i  = x%5
        print (a,b,c,d,e,f,g,h,i)
        fa = root*a
        fb = root*b
        na1 = doNote(fa,length*g,e)
        nb1 = doNote(fb,length*h,f)
        a = 8  - (x%7)
        b = 12 - (x%11)
        fa = root*a
        fb = root*b
        na2 = doNote(fa,length*g,e)
        nb2 = doNote(fb,length*h,f)
        signal=sf.Volume(na1,c)
        signal=sf.Concatenate(signal,nb1)
        signal=sf.Concatenate(signal,sf.Volume(na2,d))
        signal=sf.Concatenate(signal,nb2)
        leftBias  = i/4.0
        rightBias = 1.0-leftBias
        leftT     = 30*leftBias
        rightT    = 30*rightBias
        signal=sf.Normalise(signal)
        sl=sf.NumericVolume(+signal,leftBias)
        sr=sf.NumericVolume( signal,rightBias)
        notesL.append((sl,at+leftT))
        notesR.append((sr,at+rightT))
        at=at+length/4
        
    def mixL():
        return sf.FixSize(sf.WaveShaper(-0.03,0.2,0,-1,0.2,2,sf.Normalise(sf.MixAt(notesL))))  
    def mixR():
        return sf.FixSize(sf.WaveShaper(-0.03,0.2,0,-1,0.2,2,sf.Normalise(sf.MixAt(notesR))))  
    ret=(sf_do(mixL),sf_do(mixR))
    return ret

(left,right)=makeTrack()
left=left.get()
right=right.get()

lr=sf.Length(+right)
ll=sf.Length(+left)
if(lr>ll):
    left=sf.Concatenate(left,sf.Silence(lr-ll))
elif(ll>lr):
    right=sf.Concatenate(right,sf.Silence(ll-lr))

def OnTop(signal,root):
    inp=sf.Saturate(sf.FixSize(+signal))
    x=sf.RBJPeaking(+inp,root*20,0.25,24)
    y=sf.RBJPeaking(+inp,root*25,0.25,24)
    z=sf.RBJPeaking( inp,root*30,0.25,24)
    x=sf.Saturate(sf.FixSize(x))
    y=sf.Saturate(sf.FixSize(y))
    z=sf.Saturate(sf.FixSize(z))
    return sf.FixSize(sf.Mix(signal,sf.Pcnt15(x),sf.Pcnt_10(y),sf.Pcnt10(z)))
            
left=sf.Realise(OnTop(left,root))
right=sf.Realise(OnTop(right,root))
sf.WriteFile32((+left,+right),"temp/temp.wav")

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 
            return signal_
        else:
            -convol
            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_i,at)=grain
            out.append((reverbInner(signal_i,+convol_,grainLength),at))
        -convol_
        return sf.Realise(sf.Normalise(sf.MixAt(out)))
    return sf_do(reverberateDo)

 
(convoll,convolr)=sf.ReadFile("temp/revb.wav")

wleft =reverberate(+left,convoll)
wright=reverberate(+right,convolr)
wleft=wleft.get()
wright=wright.get()

left_out=sf.Normalise(sf.MixAt(
    (sf.Pcnt60(+wleft),10),
    (sf.Pcnt10(+wright),40),
    (sf.Pcnt10(+wleft),120),
    (sf.Pcnt15(+left),0),
    (sf.Pcnt5(+right),110)
))

right_out=sf.Normalise(sf.MixAt(
    (sf.Pcnt70(+wright),10),
    (sf.Pcnt10(wleft),40),
    (sf.Pcnt10(wright),130),
    (sf.Pcnt20(right),0),
    (sf.Pcnt5(left),105)
))
#left  = sf.Realise(left)
#right = sf.Realise(right)
sf.WriteFile32((left_out,right_out),"temp/temp_post.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")


Saturday, 22 February 2014

Downloads

Welcome To The Sonic Field Download Page

News:

Sonic Field 1.0.0 is released. This is a beta release of the first Sython (Sonic Field Python) version. My aim is to stabilise this release progressively till it is considered stable at 1.1. During this period I hope not to make any large architectural changes. The fundamental implementation of Synthon will remain the same. Additions to the language will come from additional Python modules.

Blessed Be The Cheese Makers

License:

Sonic Field is licensed under AGPL3. The bundled Jython jar is licensed under its own license - seee www.jython.org

Those found abusing the license terms will be banished to Castle Anthrax (which might not be such a bad thing)

No - seriously - do not mess with copy right. If you are interested in other licensing terms for any of my code - just ask and we will see what we can do.

Releases And Downloads:




And never forget that he is not the messiah, he is a very naughty boy. 

Friday, 21 February 2014

The Code Behind Valley Of The Sythons

The entire patch and some description of how it works beneath that:

The key to understanding what comes next is that Sonic Field now works as a big extension to Python (Jython actually - Python running on the Java Vertual Machine). A Sonic Field patch is created using Python statements. However, the data being passed around is not Python Data it is hidden from the view of Python inside opaque Java objects. 

Valley Of The Sythons

The original idea was that Sonic Field sounds were 'signals' which were passed between 'processors'. Control of processing was also done via signals. The metaphor continues into Sython (Sonic Field Python). The syntax of Python makes the approach less obvious but it is still there.

sf.Multiply(sf.NumericShape((0,0),(len,1)),trem)

For example the above creates a signal which starts at 0 and works up to 1 at length len in milliseconds. The signal is then multiplied with another signal held in variable trem.  All Sonic Field processors are exposed to Python as methods on the sf object.

One last example before the code dump:

sf.Monitor(sf.SineWave(1000,440))

The above is a very simple Sython patch. It just makes an A4 tone for one second. However, the tone will have clicks at each end because it has no attack or release. So:

sf.Monitor(
    sf.Multiply(
        sf.SimpleShape((0,-90),(100,0),(900,0),(1000,-90)),
        sf.SineWave(1000,440)
    )
)

Now that does have an attack and release so it will sound much more like the beep one might expect.

Valley Of The Sythons:

import math
import random

execfile("patches/python/concurrent.py")

def randWalk(value,size,uBound):
    value  = float(value)
    size   = float(size)
    uBound = float(uBound)
    r=random.random()
    r=math.floor(r*size)-math.floor((size/2.0))    
    value+=r
    if value<1:
        value=2
    elif value>uBound:
        value=uBound-2
    return value

def randWalk3(value,uBound):
    return randWalk(value,3,uBound)

def fixSize(signal):
    mag=sf.MaxValue(signal)
    return sf.NumericVolume(signal,1.0/mag)
 
def fixSizeSat(signal):
     return fixSize(sf.Saturate(fixSize(signal)))
    
def saturatedNode(beat,pPitch,pitch,a,d,s,r,v):
    def saturateNode_():
        l=a+d+s+r
        if l>beat*2:
            iPitch=(pitch+pPitch)/2.0
            pos=beat/8
            signal1=sf.Slide((0,iPitch),(pos,pitch),(l,pitch))
            signal2=sf.Slide((0,iPitch),(pos,pitch*2),(l,pitch*2.02))
            signal3=sf.Slide((0,iPitch),(pos,pitch*3),(l,pitch*3.03))
        else:
            signal1=sf.SineWave(l,pitch)
            signal2=sf.SineWave(l,2*pitch*1.003)
            signal3=sf.SineWave(l,3*pitch*1.005)
            
        envelope= sf.NumericShape(
                 (0,0),
                 (a,1),
                 (a+d,0.75),
                 (a+d+s,0.25),
                 (a+d+s+r,0)
        )
        
        sat=(20-pitch/1000)
        if sat<1:
            sat=1
                        
        def doSat(sigIn):
            temp=sf.NumericVolume(sf.Multiply(sigIn,envelope),sat)
            return sf.Normalise(sf.Clean(sf.Saturate(temp)))

        signal=sf.Mix(
            doSat(signal1),
            sf.DB_6(doSat(signal2)),
            sf.DB_15(doSat(signal3))
        )
        
        envelope= sf.NumericShape(
                 (0,0),
                 (a,0.1),
                 (a+d,0),
                 (a+d+s,0.1),
                 (a+d+s+r,0)
        )
        
        signal=sf.Mix(
            sf.Multiply(
                sf.ButterworthLowPass(sf.WhiteNoise(l),pitch*5,1),
                envelope),
            signal
        )
        
        signal=fixSize(signal)
        
        hf=sf.Clip(sf.NumericVolume(signal,3))
    
        r1=fixSizeSat(sf.RBJPeaking(hf,pitch*1.3,0.5,85))
        r2=fixSizeSat(sf.RBJPeaking(hf,pitch*2.1,0.5,85))
        r3=fixSizeSat(sf.RBJPeaking(hf,pitch*2.9,0.5,85))
    
        signal=sf.Mix(
            sf.DB_6(signal),
            sf.DB_1(r1),
            sf.DB_4(r2),
            sf.DB_6(r3)
        )
        
        signal=sf.Clean(sf.NumericVolume(signal,v))

        signal=sf.BesselLowPass(signal,pitch*2,4)

        envelope= sf.NumericShape(
                 (0,1),
                 (a+d+s+r-125,1),
                 (a+d+s+r,0)
        )
        signal=sf.Multiply(envelope,signal)
        
        trem=sf.Slide((0,6*random.random()),(l,0.5*random.random()))
        trem=sf.Multiply(sf.NumericShape((0,0),(l,1)),trem)
        trem=sf.Mix(
            sf.NumericShape((0,1),(l,1)),
            trem
        )
        return sf.Multiply(signal,trem)
        
    return sf_do(saturateNode_)

def run(pitch,beat,minutes,startP,initial,overV):
    notesL=[]
    notesR=[]
    oPitch=float(pitch)
    pitchScaleDenom = 1.0
    pitchScaleNume  = float(startP)
    lengthScale     = 4.0
    volumeScale     = 4.0
    oVolume         = 4.0
    at=beat*float(initial)
    pPitch=float(pitch)

    while at/60000 < minutes:
        pitchScale = pitchScaleNume/pitchScaleDenom
        rvs        = 1.0/volumeScale
        volume     = rvs*oVolume
        pitch      = pitchScale*oPitch
        length     = lengthScale*beat

        
        # Create a consistent envelope
        a          = length*0.25
        d          = length*0.5
        s          = length*1.0
        r          = length*2.0
        if a<50:
            a=50
        if d<50:
            d=50
        if a>d-50:
           a=d/2
        
        r=r-s-d-a
        s=s-d-a
        d=d-a       
        
        vCorrection = 1/pitchScale
        
        # Do not over correct very & v high low frequencies 
        #  or very quiet notes. This aim it to stop loud highs
        #  dominating (psycho-acoustics)
        if rvs<0.2:
            if vCorrection<1:
                vCorrection=1
        
        if vCorrection>4:
            vCorrection=4
                                  
        print (
            at,
            "PitchNume: ",  pitchScaleNume,
            "PitchDenom: ", pitchScaleDenom,
            "Volume: ",     volumeScale,
            "Pitch: ",      pitch,
            "Length: ",     length,
            "Rvs: ",        rvs,
            "VCorr: ",      vCorrection
        ).__str__()    
            
        signal = saturatedNode(
            beat,
            pPitch,
            pitch,
            a,
            d,
            s,
            r,
            volume * vCorrection
        )

        lr=random.random()
        rl=1.0-lr
        notesL.append([sf.NumericVolume(signal,lr),at+30*rl])
        notesR.append([sf.NumericVolume(signal,rl),at+30*lr])

        at+=length
        
        pitchScaleDenom = randWalk3(pitchScaleDenom,10)

        pitchScaleNume  = randWalk3(pitchScaleNume,16)
        
        lengthScale     = randWalk3(lengthScale,8)

        volumeScale     = randWalk3(volumeScale,8)
        
        pPitch          = pitch

    return (
        sf.NumericVolume(sf.Normalise(sf.Clean(sf.MixAt(notesL))),overV),
        sf.NumericVolume(sf.Normalise(sf.Clean(sf.MixAt(notesR))),overV)
    )

def compressInner(signal,amount):
    def compressInnerDo():
        if sf.MaxValue(signal)<0.001:
            return signal
        signal_=sf.Normalise(signal)
        stf=sf.Normalise(sf.ButterworthLowPass(signal_,128,2))
    
        offset=1.0-amount    
        sr=sf.Reverse(sf.Follow(sf.Reverse(stf),1,1024))
        sw=sf.Follow(stf,1,1024)
        shape=sf.Mix(sr,sw)
        div=1.0/sf.MaxValue(shape)
        shape=sf.NumericVolume(shape,div)
        shape=sf.DirectMix(offset,sf.NumericVolume(shape,amount))
        return sf.Normalise(sf.Divide(signal_,shape))
    return sf_do(compressInnerDo)

def compress(signal,amount):
    def compressDo():
        cpo=amount
        signalM=sf.BesselBandPass(signal,200,2000,4)
        signalH=sf.BesselHighPass(signal    ,2000,4)
        signalL=sf.BesselLowPass( signal    , 200,4)
        amount_=cpo*cpo 
        
        signalM=compressInner(signalM, amount_)
        signalH=compressInner(signalH, amount_)
        signalL=compressInner(signalL, amount_)
    
        return sf.Normalise(sf.MixAt(
            (sf.Pcnt40(signalL),3.5),
            (sf.Pcnt20(signalM),0.0),
            (sf.Pcnt40(signalH),0.0)
        ))
    return sf_do(compressDo)
    

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)

def doRun1():
   return run(128,1024          ,6,1,0,1.0)
def doRun2():
   return run(128.0*4.0/3.0,1024,6,2,1,1.0)
def doRun3():
   return run(256.0*3.0/2.0,1024,6,1,5,0.5)
def doRun4():
   return run(512.0*5.0/4.0,1024,6,1,9,0.25)

random.seed(0.128)

x1=sf_do(doRun1)
x2=sf_do(doRun2)
(left1,right1) = x1.get()
sf.WriteSignal(left1,"temp/l1")
sf.WriteSignal(right1,"temp/r1")

(left2,right2) = x2.get()
sf.WriteSignal(left2,"temp/l2")
sf.WriteSignal(right2,"temp/r2")

x3=sf_do(doRun3)
x4=sf_do(doRun4)

(left3,right3) = x3.get()
sf.WriteSignal(left3,"temp/l3")
sf.WriteSignal(right3,"temp/r3")

(left4,right4) = x4.get()
sf.WriteSignal(left4,"temp/l4")
sf.WriteSignal(right4,"temp/r4")


left1=sf.ReadSignal("temp/l1")
left2=sf.ReadSignal("temp/l2")
left3=sf.ReadSignal("temp/l3")
left4=sf.ReadSignal("temp/l4")
left  = sf.Normalise(sf.Clean(fixSize(sf.Mix(left1,left2,left3,left4))))
left  = compress(left,0.33)
sf.WriteSignal(left,"temp/l")
left=""

right1=sf.ReadSignal("temp/r1")
right2=sf.ReadSignal("temp/r2")
right3=sf.ReadSignal("temp/r3")
right4=sf.ReadSignal("temp/r4")
right = sf.Normalise(sf.Clean(fixSize(sf.Mix(right1,right2,right3,right4))))

right = compress(right,0.33)
sf.WriteSignal(right,"temp/r")
right=""

sf.WriteFile32((sf.ReadSignal("temp/l"),sf.ReadSignal("temp/r")),"temp/temp.wav")

(left,right)=sf.ReadFile("temp/temp.wav")

(convoll,convolr)=sf.ReadFile("temp/terrys_warehouse_stereo_short.wav")
convoll=sf.Mix(
    convoll,
    sf.Pcnt15(sf.DirectRelength(convoll,0.2)),
    sf.Pcnt15(sf.Raise(sf.DirectRelength(convolr,0.2),2))
)
convolr=sf.Mix(
    convolr,
    sf.Pcnt15(sf.DirectRelength(convolr,0.2)),
    sf.Pcnt15(sf.Raise(sf.DirectRelength(convolr,0.2),2))
)
convoll=sf.Normalise(sf.Saturate(sf.Normalise(convoll)))
convolr=sf.Normalise(sf.Saturate(sf.Normalise(convolr)))

wleft =reverberate(left,convoll)
wright=reverberate(right,convolr)

left=sf.Normalise(sf.MixAt(
    (sf.Pcnt70(wleft),10),
    (sf.Pcnt10(wright),40),
    (sf.Pcnt20(left),0)
))

right=sf.Normalise(sf.MixAt(
    (sf.Pcnt70(wright),10),
    (sf.Pcnt10(wleft),40),
    (sf.Pcnt20(right),0)
))

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

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

left  = compress(left,0.95)
right = compress(right,0.95)

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

shutdownConcurrnt()

First the dirty! Why 'execfile("patches/python/concurrent.py")' The answer is that I could not be bothered to set up sys.path or the class path correctly - me bad :( [I have fixed it in later patches]

Now for the Random Walk code:


def randWalk(value,size,uBound):
    value  = float(value)
    size   = float(size)
    uBound = float(uBound)
    r=random.random()
    r=math.floor(r*size)-math.floor((size/2.0))    
    value+=r
    if value<1:
        value=2
    elif value>uBound:
        value=uBound-2
    return value

def randWalk3(value,uBound):
    return randWalk(value,3,uBound)

The core concept behind the piece is constraining randomness to give patters which shift around slowly forming shape and movement in the piece. Here we see a few key points. Working with Random numbers requires a close interaction between integer and non integer numbers. randWalk take an number (assumed to be an integer) and moves it randomly up or down. However, the maximum distance it can move is fixed by the size parameter. The maximum value it can reach is fixed by the uBound parameter and the minimum is 1. This causes the 'random walk' effect that the music is based upon. randWalk3 is simply a helper function (I prefer this to default parameters in some cases as it is more explicit).

        if l>beat*2:
            iPitch=(pitch+pPitch)/2.0
            pos=beat/8
            signal1=sf.Slide((0,iPitch),(pos,pitch),(l,pitch))
            signal2=sf.Slide((0,iPitch),(pos,pitch*2),(l,pitch*2.02))
            signal3=sf.Slide((0,iPitch),(pos,pitch*3),(l,pitch*3.03))
        else:
            signal1=sf.SineWave(l,pitch)
            signal2=sf.SineWave(l,2*pitch*1.003)
            signal3=sf.SineWave(l,3*pitch*1.005)

The above piece of code is interesting as it alters note articulation based on note length. Short notes will have the same pitch throughout. However, longer notes will have a short 'slur' or 'slide' between them by bending the start of the next note to the average to the two.

We can also see here that each note it made from 3 tones. However, what we hear in Valley is very much more harmonically rich than that.

        envelope= sf.NumericShape(
                 (0,0),
                 (a,1),
                 (a+d,0.75),
                 (a+d+s,0.25),
                 (a+d+s+r,0)
        )
        
        sat=(20-pitch/1000)
        if sat<1:
            sat=1
                        
        def doSat(sigIn):
            temp=sf.NumericVolume(sf.Multiply(sigIn,envelope),sat)
            return sf.Normalise(sf.Clean(sf.Saturate(temp)))

        signal=sf.Mix(
            doSat(signal1),
            sf.DB_6(doSat(signal2)),
            sf.DB_15(doSat(signal3))
        )

The addition of harmonic complexity is done with the above code. First we create a standard ADSR envelope. Then we work out an number related to pitch which will be used to control the amount of harmonic richness to add. The reason to base it on pitch is that physical instruments tend to have more harmonic in their lower registers and so mimicking this mathematically produces sounds which are more interesting to listen to. 

        @Override
        public double getSample(int index)
        {
            double x = getInputSample(index);
            double y = x >= 0 ? x / (x + 1) : x / (1 - x);
            return y;

        }

The above is the Java (remember that audio processing heavy work in Sonic Field is done in Java not Python). It is a rather magical formula because it is so simple and yet so effective. It simply forces any value in the incoming signal to fit between 1 and -1. It does this by asymptotically crushing the signal as it approaches 1 or -1.
X and X/(X+1)
I came up with the idea of using this as a audio processor (strictly a wave shaper) one evening whilst working in Cambridge a couple of years ago. It is so simple and yet so effective, I could not believe my luck in thinking of it (I was dreaming of complex polynomials and logs and things). We can see that to begin with (X near 0) X and X(X+1) are similar but as X grows the processed wave bends over to approach 1 (and -1 for the X/(1-X) version for negative numbers). As a result, the wave form is distorted to become closer to a square wave. This add odd harmonics. The larger the amplitude of the incoming wave the more the distortion and the greater the addition of harmonics. A sine wave a large magnitude entering the wave shaper will come out as a rounded square wave.

The effect of greater amplitude -> greater harmonic content also mimics natural instruments. By using the saturate processor after the application of an envelope we make the harmonic content follow the envelope just as it does with - for example - a Sax where the louder the note the 'brighter' it sounds. 

The link between pitch and harmonic content is performed the same way:

        def doSat(sigIn):
            temp=sf.NumericVolume(sf.Multiply(sigIn,envelope),sat)
            return sf.Normalise(sf.Clean(sf.Saturate(temp)))

We use the saturation processor on the output result of setting the over all volume (amplitude) of the signal by the variable sat. sat is bigger for lower notes and so amplitude will be bigger and so the harmonic content larger.

Note:

  1. sf.Clean removes higher frequencies using a special finite impulse response filter to avoid build up of those frequencies. This prevents further processing causing harmonics of high frequencies getting so high the alias.
  2. sf.Normalise removes any DC from the signal and sets the maximum excursion to 1 by scaling the whole signal. By DC I mean, the sum of all the samples in the signal is the DC component. Build up of DC is a constant problem in digital processing which does not happen in analogue as the capacitors used to link circuits automatically remove all DC.

Finally for this section: why three signals? I leave that up to you to think about.

Next - resonance and body sounds


        hf=sf.Clip(sf.NumericVolume(signal,3))
    
        r1=fixSizeSat(sf.RBJPeaking(hf,pitch*1.3,0.5,85))
        r2=fixSizeSat(sf.RBJPeaking(hf,pitch*2.1,0.5,85))
        r3=fixSizeSat(sf.RBJPeaking(hf,pitch*2.9,0.5,85))

When a real instrument is played it shakes. For strings the shanking in part of the projecting of the sound. For brass, it produces a percussive timbre on top of the fundamental sound of the instrument. In the patch fragment above, I am attempting to mimc the effect of such shaking. This is done by passing the signal into infinite impulse response filters which are set to near resonance. Any signal passed into them which contains frequencies near to their resonant frequency will cause them to ring.

The 'near to their resonant frequency' is important. They will not resonate if signal is passed in which does not contain the required references. We can see here that I have not set their resonant frequencies to those of the notes so how will they resonate? The trick is in the sf.Clip. This hard limits signals so that if a sample goes above 1 it is set to 1 and if it goes below -1 it is set to -1. That hard limiting sprays frequencies all over the spectrum (think electric guitar fuzz). The resonators can pick up some of that sprayed frequency and resonate form it. Because the clipping will be dependant of amplitude of the signal the resonance will as well, which again, is the way physical instruments tend to work.

In my next post I will discuss compression, reverberation and well the Sonic Field - that for which Sonic Field was first created.