Sunday, 19 October 2014

Not Just A Saw Tooth - Generative Complex, Realistic Sounds

The waveform

The common literature on synthesis talks of sawtooth, triangle and square waves. Real sounds are not like this so why do we even discuss them?

Above is the wave form of the first note from my synthesis of BWV 659:


It does not look much like a sawtooth does it? Indeed, that 'presence of God" massive G1 is nothing like any geometrical waveform you will find in any text book or on the front panel of any regular synthesiser.

Spectrum Visualisation

Spectrum Analysis
Interesting sounds have harmonics (partials etc) which move in phase constantly. The pitch the notes are unstable and shifting. The pitch and harmonic content changes though out the note. On top of all of this, they have noise in different colours.

Below are some parts of the code used to create the G1. The final envelope and filter stuff is missing. I am not putting it here to be definitive but to point some stuff out. Like, for example, the additive oscillator bank. Note that to generate just the basic tone I am using 19 'oscillators'. They have random phases each time they are run and they make a harmonic progression with even harmonics begin 180 degrees out of phase with odd ones.

     p1=random.random()
     p2=1.0-p1
...
       sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p1),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*2.0,p1),2.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p2),2.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*4.0,p1),1.8),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p2),1.6),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*6.0,p1),1.4),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p2),1.2),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*8.0,p1),1.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*9.0,p2),0.8),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*10.0,p1),0.6),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*11.0,p2),0.5),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*12.0,p1),0.4),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*13.0,p2),0.3),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*14.0,p1),0.2),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*15.0,p2),0.1),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*16.0,p1),0.05),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*16.0,p2),0.05),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*17.0,p1),0.05),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*18.0,p2),0.05),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*19.0,p1),0.01)
        )
    sig=sf.Multiply(
        sf.NumericShape((0,0),(32,1),(length,1)),
        sig
    )
...
    b=posaunePulse(length,freq)
    b=sf.MixAt(
        [b,12],
        [
        sf.NumericShape(
            (0, -2.0),
            (4,  2.0),
            (12,-1.00),
            (20, 1.00),
            (28,-1.00),
            (length,0)
        ),0]
    )
    b=sf.RBJPeaking(b,freq*2,2,2)
    b=polish(b,freq)
    sig=sf.Mix(
        b
        ,
        sf.Pcnt20(sf.Multiply(+b,sf.WhiteNoise(length))),          
        sf.Multiply(
            cleanNoise(length,freq*0.5),
            sf.SimpleShape((0,-60),(64,-14),(128,-28),(length,-24))
        )
    )

    return pitchMove(sig)
...
def pitchMove(sig):
    l=sf.Length(+sig)
    if l>1024:
        move=sf.NumericShape(
            (0,0.995+random.random()*0.01),
            (l,0.995+random.random()*0.01)
        )
    elif l>512:
        move=sf.NumericShape(
            (0,0.9975+random.random()*0.005),
            (l,0.9975+random.random()*0.005)
        )
    else:
        return sig

    return sf.Clean(sf.Resample(move,sig))

Next I mix in a 'clunk' sound to model the valve of the pipe opening. This is done using an envelope and a low pass filter. I throw in a resonant all pass filter and a filter to remove aliasing. The signal ins ring modulated (multiplied by) filtered white noise to mimic the sound of air rushing over the reed. Some enveloped white noise is added as well.

Finally, the pitch of the note is made to move slightly throughout its sounding. Actually, this is (as I said) not the whole story as there is another section which applies filtering, and a volume envelope which is also used to very slightly frequency modulate the sound to mimic the way real pitch changes with volume. The envelope applied here is not only dependant on the length of the note but on the length and proximity of the notes either side of it.

The sound you hear is actually three pipes. Each with very, very slightly differently out of tune, sounding three octaves (G1, G0 and G-1). But that is not all as the three are then places at slightly different times to one another and with respect too the left and right channels.

Then some harmonic excitement and filtering and two different impulse response reverberations are applied.

All that is what is required to make that one, single note.

A far cry from 'what does a saw tooth sound like'.

Saturday, 11 October 2014

Bringing Flute Pipes To Sonic Field

For this piece I really wanted a very sweet sounding flute pipe:


I found the sound I wanted in the strangest place. Sawtooth waves sound harsh; they can be tamed, but they are never sweet in the way I imagined the flutes to be. Nevertheless, by adding an extra parameter to a simple additive sawtooth generator flutes and strings suddenly appeared! 

In a sawtooth each overtone (harmonic) is scaled down as 1/n where n is the frequency ratio of the overtone. So, the 1st harmonic at 2 times the frequency has 1/2 the volume; the second harmonic at 3 times the frequency is 1/3 the volume. Now, raise that ratio of a power, say 0.5 or 2.0 and so forth. Low powers increase the harmonic content and a string sound appears, how powers decrease it and bright or sweet (even lower content) flutes appear. Here is the code:

def makeSimpleBase(length,frequency,z):
    p=random.random()
    if frequency>4000:
        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*2.0,p),(1.0/2.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),(1.0/3.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*4.0,p),(1.0/4.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),(1.0/5.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*6.0,p),(1.0/6.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),(1.0/7.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*8.0,p),(1.0/8.0)**z)
            )
    else:
        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*2.0,p),(1.0/2.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),(1.0/3.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*4.0,p),(1.0/4.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),(1.0/5.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*6.0,p),(1.0/6.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),(1.0/7.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*8.0,p),(1.0/8.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*9.0,p),(1.0/9.0)**z),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*10.0,p),(1.0/10.0)**z)
        )
    return sf.FixSize(sig)

def niceSaw(length,frequency):
    return makeSimpleBase(length,frequency,1.0)

def violaBase(length,frequency):
    return makeSimpleBase(length,frequency,0.5)

def sweetFluteBase(length,frequency):
    return makeSimpleBase(length,frequency,8.0)
    
def brightFluteBase(length,frequency):
    return makeSimpleBase(length,frequency,3.5)

def celestFlute(length,freq):
    sig=sf.Mix(
        sf.Pcnt50(sweetFluteBase(length,freq)),
        sf.Pcnt50(sweetFluteBase(length,freq+1.0)),
        sf.Multiply(
            cleanNoise(length,freq*0.5),
            sf.SimpleShape((0,-60),(64,-28),(128,-40),(length,-40))
        )
    )
    return pitchMove(sig)

def sweetFlute(length,freq):
    sig=sf.Mix(
        sweetFluteBase(length,freq),
        sf.Multiply(
            cleanNoise(length,freq*0.5),
            sf.SimpleShape((0,-60),(64,-30),(128,-40),(length,-40))
        )
    )
    sig=sf.FixSize(polish(sig,freq))
    return pitchMove(sig)

def brightFlute(length,freq):
    sig=sf.Mix(
        brightFluteBase(length,freq),
        sf.Multiply(
            cleanNoise(length,freq*0.5),
            sf.SimpleShape((0,-60),(64,-28),(128,-40),(length,-40))
        )
    )
    sig=sf.FixSize(polish(sig,freq))

    return pitchMove(sig)


Thursday, 9 October 2014

Bombard Pipe

A bombard should make you sit up!

A bombard is a BIG sound. Not quite an ophicleide (how the heck does one pronounce that?) but it should be at least a little intimidating. I am trying to make it happen in Sonic Field. My sounds seem to keep coming out to nice; I need some more wollop. But here is where I am so far.

def bombardPulse(length,frequency):
    p=random.random()
    if frequency>4000:
        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*2.0,p),2.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),1.5),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*4.0,p),1.3),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),1.2),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*6.0,p),1.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),0.8),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*8.0,p),0.5)
            )
    else:
        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*2.0,p),2.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),1.5),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*4.0,p),1.3),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),1.2),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*6.0,p),1.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),0.8),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*8.0,p),0.6),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*9.0,p),0.4),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*10.0,p),0.2)
        )

    return sf.FixSize(sig)

def bombard(length,freq):
    b=sf.MixAt(
            [sf.Pcnt33(bombardPulse(length,freq)),0],
            [sf.Pcnt33(bombardPulse(length,freq)),10],
            [sf.Pcnt33(bombardPulse(length,freq)),20]
    )
    sig=sf.Mix(
        b
        ,
        sf.Pcnt10(sf.Multiply(+b,sf.WhiteNoise(length))),          
        sf.Multiply(
            cleanNoise(length,freq*0.5),
            sf.SimpleShape((0,-60),(64,-20),(128,-28),(length,-24))
        )
    )

    return pitchMove(sig)

So the first thing which is very different from anything I have tried before is the initial wave form. Rather than the more usual decreasing harmonics (each overtone being weaker than the previous and weaker than the fundamental) here I use additive synthesis to create overtones which have greater magnitude than the fundamental. 

The next trick to make a bigger sound is to use three sounds each 10 milliseconds after the previous and mix them. This gives an 'entrance' to the note.

Finally, rather than just mixing some filtered noise with the waveform I add some filtered noise multiplied by it as well. This makes a noise sound modulated by the wave form; if one imagines air rushing over the reed of a massive organ pipe the idea becomes more obvious.

Did it work? Well, it is getting there but I believe I can do even better. The bombard makes some of the bass this piece (the rest is an even richer reed sounds which I will discuss another time):

BWV 645 By JS Bach

  

Saturday, 4 October 2014

Python Plays Bach

I showed this Youtube video to a respected colleague and massive Python fan a few days ago. I never expected the reaction I got.

Bach Passacaglia and Fugue in C Minor

He thought my efforts in Python produced the spectrogram which I used for the visual part of the video. Whilst I would love to take credit, they are actually produced via ffmpeg.


When I explain to him that Python made the music; he did not believe me. He said that I was 'having him on' and generally taking the piss. When I further went on to explain that the sounds are not samples, but generated from pure mathematics, the only way to get him to believe my words was to show him some Python...

On github:

  1. https://github.com/nerds-central/SonicFieldRepo/blob/master/SonicField/scripts/python/Bach-Large-Organ.sy
  2. https://github.com/nerds-central/SonicFieldRepo/blob/master/SonicField/scripts/python/reverberate.sy

The programming model is that Python control the sounds and Java does the mathematical heavy listing. For example, here is the FFT code from Sonic Field:
  1. https://github.com/nerds-central/SonicFieldRepo/blob/master/SonicField/src/com/nerdscentral/audio/pitch/algorithm/FFTbase.java
  2. https://github.com/nerds-central/SonicFieldRepo/blob/master/SonicField/src/com/nerdscentral/audio/pitch/algorithm/CacheableFFT.java



He believed me when he saw code: It has been an amazing journey to get to such a point where Sonic Field has started to sound good enough to not be 'synthy'. It am very proud to have proven it can be done, that samples are not required to make beautiful sounds. Clearly, some people will not find the sounds my code has made pleasing, some will be shocked at how synthetic they sound. But at least now we can say at Python, at least for some people, makes music.

Thursday, 2 October 2014

Stopped Reed: Warm To Sparkling

I don't actually know if anyone produces stopped reed pipes. In truth, it is not really necessary as a cylindrical bore read would make much the same sound. Anyhow - it was a sound I had in my head so here is how I tried to make it:


def stoppedPulse(length,frequency):
    p=random.random()
    if frequency>3000:
        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),1.0/1.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),1.0/1.5),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),1.0/2.0)
            )
    else:
        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),1.0/1.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),1.0/1.5),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),1.0/2.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*9.0,p),1.0/4.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*11.0,p),1.0/8.0)
        )
    return sf.FixSize(sig)

def stoppedReed(length,freq):
    s1=stoppedPulse(length,freq*1.000)
    s1=sf.ButterworthHighPass(s1,freq*0.66,6)
    s1=sf.Clean(s1)
    
    sig=sf.Mix(
        s1,
        sf.Multiply(
            cleanNoise(length,freq*2.0),
            sf.SimpleShape((0,-60),(64,-16),(128,-20),(length,-20))
        )
    )

    sig=sf.FixSize(sig)
    sig=sf.Mix(
        sf.Pcnt10(sf.Clean(sf.Saturate(+sig))),
        sig
    )
    sig=sf.ButterworthHighPass(sig,freq*0.66,6)
    sig=sf.Clean(sig)

    return sf.FixSize(sf.Clean(sig))

The voice is simple enough, it takes a clean sound from additive synthesis, adds some enveloped, filtered white noise and makes sure no nasty aliasing artefacts sneak in. To brighten it up a little, I have added a touch of sf.Saturate. This tends to add mainly odd harmonics anyhow - so it is a good way to add some colour to what was otherwise rather sterile.

Normally, we might try to make the 'odd harmonic only' sound of a stopped pipe or a cylindrical read (think clarinet) sound using square waves. I did not do this. The waves I used have a much larger contributions from the early harmonics and stop short to ensure no aliasing happens.

        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),1.0/1.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),1.0/1.5),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),1.0/2.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*9.0,p),1.0/4.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*11.0,p),1.0/8.0)
        )

 Here we can see that I am adding only odd harmonics but that the third is actually great a magnitude as the fundamental and that the harmonics die away slowly. This gives a more reed sound; something brighter and potentially more harsh. However, in the later stages of processing filtering takes some of these higher harmonics back down. (note that for this sound, the subBass granular processing is turned off).

        if pitch<256:
            if subBass:
                if pitch < 128:
                    sig=sf.Mix(
                        granularReverb(+sig,ratio=0.501 ,delay=256,density=32,length=256,stretch=1,vol=0.20),
                        granularReverb(+sig,ratio=0.2495,delay=256,density=32,length=256,stretch=1,vol=0.10),
                        sig
                    )
                elif pitch < 192:
                    sig=sf.Mix(
                        granularReverb(+sig,ratio=0.501,delay=256,density=32,length=256,stretch=1,vol=0.25),
                        sig
                    )
                else:
                    sig=sf.Mix(
                        granularReverb(+sig,ratio=0.501,delay=256,density=32,length=256,stretch=1,vol=0.15),
                        sig
                    )
            sig=sf.BesselLowPass(sig,pitch*8.0,2)
        if pitch<392:
            sig=sf.BesselLowPass(sig,pitch*6.0,2)
        elif pitch<512:
            sig=sf.Mix(
                sf.BesselLowPass(+sig,pitch*6.0, 2),
                sf.BesselLowPass( sig,pitch*3.0, 2)
            )                
        elif pitch<640:
            sig=sf.BesselLowPass(sig,pitch*3.5, 2)
        elif pitch<1280:
            sig=sf.Mix(
                sf.BesselLowPass(+sig,pitch*3.5, 2),
                sf.BesselLowPass( sig,pitch*5.0, 2)
            )                
        else:
            sig=sf.Mix(
                sf.BesselLowPass(+sig,pitch*5, 2),
                sf.BesselLowPass( sig,5000,    1)
            )


That combination give rich, warm low tones and bright sparkling high notes. I am really taken with the effect even it I do say so myself. In this video the upper two voices are both generated using this voice:


Sunday, 28 September 2014

Reverb's So Real You Cannot Hear It

If I said this myself - then I would be boasting but this is a paraphrase of what someone else said about this double reverb' effect.

What (if I remember correctly) he said was that he was expecting the effect but was confused because he did not hear it. Then he realised it must be there because the music would not sound like that if there was no reverb'; "Yes, it sounds like is it played in a room, but not like a reverb' effect".

My contention is that we have gotten very used to the sound of reverberation effects. We are used to them to such and extent that we expect that artificial sound of early reverberation units and forget that the original idea was to mimic real spaces. Indeed, even impulse response reverberation does not sound real.

How can impulse response reverberation not sound real?

I have a theory as to how this happens. Consider that you are going to make an impulse response reverberation for cathedral. One way of doing this is to set up your equipment in the nave or choir and send out your frequency sweeps. You measure the returned sound and compute the impulse response.

What actually happens when music is created in such a space is very different. The sound is typically made near a wall and other reflective surfaces and then heard by the listener some distance away. My guess is that the upper frequencies of the early reflections are more highlighted this way. Indeed, some early reflections will be sound bright they will interfere with the original signal and make the upper frequencies dance.

What could be more this way than a pipe organ in a church or cathedral? The instrument is always near a wall; what is more that pipes themselves are reflectors of other pipes and the instrument is highly distributed through space. Somehow we must make the sound shine an vibrate (emotive words, not technical) in a way that a simple impulse response cannot.

It try and mimc the effect of listening to a large organ in a large space I have worked on this patch. As Joerg Sprave would say - "let me show you its features"

def excite(sig_,mix,power):
    def exciteInner():
        sig=sig_
        m=sf.Magnitude(+sig)
        sigh=sf.BesselHighPass(+sig,500,2)
        mh=sf.Magnitude(+sigh)
        sigh=sf.Power(sigh,power)
        sigh=sf.BesselHighPass(sigh,1000,2)
        nh=sf.Magnitude(+sigh)
        sigh=sf.NumericVolume(sigh,mh/nh)
        sig=sf.Mix(sf.NumericVolume(sigh,mix),sf.NumericVolume(sig,1.0-mix))
        n=sf.Magnitude(+sig)
        return sf.Realise(sf.NumericVolume(sig,m/n))
    return sf_do(exciteInner)
    
(convoll,convolr)=sf.ReadFile("temp/bh-l.wav")
(convorl,convorr)=sf.ReadFile("temp/bh-r.wav")

convoll=excite(convoll,0.75,2.0)
convolr=excite(convolr,0.75,2.0)
convorl=excite(convorl,0.75,2.0)
convorr=excite(convorr,0.75,2.0)

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

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

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

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

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

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

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

First notice the simple harmonic exciter. I have discussed this elsewhere. However, the first trick to getting a bright reverb' is to excite the initial impulse response:

(convoll,convolr)=sf.ReadFile("temp/bh-l.wav")
(convorl,convorr)=sf.ReadFile("temp/bh-r.wav")

convoll=excite(convoll,0.75,2.0)
convolr=excite(convolr,0.75,2.0)
convorl=excite(convorl,0.75,2.0)
convorr=excite(convorr,0.75,2.0)

Then the resulting 'wet' (i.e. reverberated) signal is excited again (just a little):

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

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

This makes a bright, vibrant sound. The impulse I am using is a lot like a large concert hall. With excitation effects added it starts to sound very bright and close and looses some of its size. I guess it is a bit like an ornate church.

I wanted to add much more size. To do this, I used my imagination rather than just mathematics. Consider listening to a organ with the nave of a huge cathedral behind you and the aisles to the sides. The bright sound will come from the organ and its surround but, somewhat delayed and at lower frequencies, will come the long, slow reverberation from those huge stone enclosed spaces. To get that effect I created a completely separate reverberation with a much longer, denser reverberation which and pre-filtered the signal to remove some of the high frequencies.

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

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

It is the mixture of a little of this very long low reverberation with the bright faster reverberation which makes the final result.

Here is an example:





Saturday, 27 September 2014

Keeping Aliasing Under Control

I have been working on a reed pipe sound. Something like a oboe but for a pipe organ. Aliasing made it really hard!

The problems stem from wanting to use saw tooth waveforms and then distortion synthesis on top. I could have used additive synthesis to create the saw tooth wave forms. This works (actually, just after writing this I did start using some additive synthesis so have added that to the bottom of the write up) but it only helps a little because of the distortion synthesis. The basic problem is that my sawtooth has aliasing in it and then adding distortion add more aliasing.

What do I mean by aliasing? I am not talking about that 8 bit effect what the waveform as squelchy noise in it. The aliasing I am talking about is frequency aliasing. 

You see, we work with twice the sample rate of the maximum frequency we want to convey. The snag is that mathematical transformations of a wave form or a simple generation of a waveform make frequencies which are above that half way point. Say that we are making a signal at 5000Hz at 96ksps (96 000 samples per second). If that signal is a saw tooth then the 6th harmonic will have energy (1/6 of the fundamental) and be of 30kHz for example. Not a problem, but what about the 10th harmonic at 1/10 the fundamental energy (only 10db down); it is at 50kHz. It is not possible to represent 50KHz at 96ksps. The 'Nyquist Frequency' is 48kHz. What happens is that the frequency 'folds' around the Nyquist frequency and we get not 50kHz but 46 (48-2).  The higher the harmonics after that, the lower they actually come out in the eventual output until the fold again at 0Hz and start coming back up.

There is another aliasing issue which occurs. Some people might not call it aliasing, but it sort of its. It is caused by negative frequencies. When we frequency modulate one signal by another we make side bands. Ring and amplitude modulation do the same thing but to a lesser extent. If the side bands are wide enough they can end up being negative. Consider a 1kHz signal modulated by a 1.1kHz signal. There will be sidebands all over the place, but the first side bands will be at 2.1 and 0.1kHz. The 0.1 is actually an alias of -0.1kHz. 

As we distort a signal using distortion synthesis we add harmonics. These can easily start to push up past the Nyquist frequency and cause problems. Also, sample wise manipulations can actually place a form of frequency modulation on a signal where by the signal is frequency modulated by the sample rate. This latter effect is very noticeable in the rather over simplistic MakeSawTooth function in Sonic Field.

The way to control these effects is to use an anti-aliasing filter sf.Clean and a high pass filter. The former stops harmonics from escaping above 22kHz so they have no chance of then being distorted into creating harmonics over the Nyquist frequency. The latter removes the low frequencies which build up due to negative frequency aliases. These low frequencies can be of very low energy but we still hear them distinctly as a metallic sound. They are very characteristic of digital synthesis and that head crushing effect harshness that can, sadly, sometime completely ruin it.

def simpleOboe(length,freq):
    sig=sf.FixSize(
        sf.Power(
            sf.Clean(
                sf.Mix(
                    sf.Clean(
                        sf.MakeSawTooth(
                            sf.PhasedSineWave(length,freq,random.random())
                        )
                    ),
                    sf.PhasedSineWave(length,freq,random.random())
                )
            )
            ,
            1.5
        )
    )
    sig=sf.ButterworthHighPass(sig,freq*0.66,6)
    sig=sf.Clean(sig)
    sig=sf.FixSize(sf.Power(sig,1.5))
    sig=sf.Clean(sig)
    sig=sf.ButterworthHighPass(sig,freq*0.66,6)
    sig=sf.FixSize(sf.Power(sig,1.5))
    sig=sf.Clean(sig)
    sig=sf.ButterworthHighPass(sig,freq*0.66,6)
    sig=sf.FixSize(sig)
      

    sig=sf.RBJPeaking(sig,freq*5,0.5,5)
    sig=sf.RBJPeaking(sig,freq*7,1,5)
    sig=sf.RBJNotch  (sig,freq*2,0.5,1)
    sig=sf.Clean(sig)
    
    sig=sf.Mix(
        sf.FixSize(sig),
        sf.Multiply(
            cleanNoise(length,freq*9.0),
            sf.SimpleShape((0,-60),(64,-20),(128,-24),(length,-24))
        )
    )

    sig=sf.ButterworthLowPass (sig,freq*9,4)
    sig=sf.ButterworthHighPass(sig,freq*0.66,6)

    return sf.FixSize(sf.Clean(sig))

In above patch which makes a Oboe like sound (to be honest, more of a reed organ pipe) I have highlighted the repeated anti-aliasing and high pass filters which make this sound usable. Without them it becomes overwhelmingly metallic and harsh.

Finally, a bit of additive synthesis helps. Rather than going a fully additive approach, I stuck with the distortion and subtractive approach buy replaced MakeSawTooth with a very simple additive sawtooth generator:

def niceSaw(length,frequency):
    p=random.random()
    if frequency>4000:
        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*2.0,p),1.0/2.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),1.0/3.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*4.0,p),1.0/4.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),1.0/5.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*6.0,p),1.0/6.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),1.0/7.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*8.0,p),1.0/8.0)
            )
    else:
        sig=sf.Mix(
            sf.PhasedSineWave(length,frequency,p),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*2.0,p),1.0/2.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*3.0,p),1.0/3.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*4.0,p),1.0/4.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*5.0,p),1.0/5.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*6.0,p),1.0/6.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*7.0,p),1.0/7.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*8.0,p),1.0/8.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*9.0,p),1.0/9.0),
            sf.NumericVolume(sf.PhasedSineWave(length,frequency*10.0,p),1.0/10.0)
        )

    return sf.FixSize(sig)