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)


No comments:

Post a Comment