Teensy Synthesizer | Waveform aliasing "Problem"

Status
Not open for further replies.

ohoomens

Member
Hey there!
First of all: Thanks Paul for this wicked platform. It opens up the world of electronics and being creative with it even for programming-noobs like me (I'm starting to get somewhere but I'm by no means a pro!).

So I'm currently working on my bachelor thesis and I've been building a touch based synthesizer based on the Teensy for about a year now. It's starting to look cool and I've got a lot working already, but currently I'm struggeling with audible aliasing effects (I think it's aliasing at least) on the waveform synthesis, especially on higher frequencies from 800hz upwards - nyquist is probably the limiting factor here? With a sine wave it's obviously not really noticable but with triangle, sawtooth and every other waveform which has high frequency components there are loads of audible artifacts. There seem to be a few other threads about this but I could not find a solution so I thought I put this up again. As far as I could identify the problem: I need to limit the bandwidth before the synthesis happens.

For all of you pros out there: How can I limit the waveform bandwith so nyquist won't destroy my beautiful sound waves? :D

Thanks in advance! :)
 
A 4th order low pass filter with 16 to 18kHz corner frequency should kill enough of the waveform's harmonics to make them inaudible.
 
A 4th order low pass filter with 16 to 18kHz corner frequency should kill enough of the waveform's harmonics to make them inaudible.

The problem is: The effect on the synthesis already happened before I send it into the mixer, so a low pass would not get rid of the low frequencies and all the stuff that has happened as an effect.

Here is an image of the spectrum:
TeensySaw.jpg
 
The problem is: The effect on the synthesis already happened before I send it into the mixer, so a low pass would not get rid of the low frequencies and all the stuff that has happened as an effect.

Here is an image of the spectrum:
View attachment 16022

IMO, what you have is that your saw signal is modulated with 200 Hz (if the frequency axis is correct).
You see that any frequency component of the saw signal has +- secondary peaks that could be 200 Hz apart similar to the LF peak (the modulation frequency)
Where are the 200 Hz coming from?
lets guess: 200 Hz is 1/5 ms, this does not fit with the block size of 128 samples (2.9 ms)
so I would search for any possible periodicity in your code that would fit with 5ms or 220 samples or similar
 
IMO, what you have is that your saw signal is modulated with 200 Hz (if the frequency axis is correct).
You see that any frequency component of the saw signal has +- secondary peaks that could be 200 Hz apart similar to the LF peak (the modulation frequency)
Where are the 200 Hz coming from?
lets guess: 200 Hz is 1/5 ms, this does not fit with the block size of 128 samples (2.9 ms)
so I would search for any possible periodicity in your code that would fit with 5ms or 220 samples or similar

Tested it without anything from my sketch, just plain one waveform (sawtooth) and playing a frequency of 1567.98 Hz (Note: G6) directly to the stereo out without any hardware attached. Tried it with a grounded and a floating system (battery bank + laptop). Any further ideas? :D
 
Tested it without anything from my sketch, just plain one waveform (sawtooth) and playing a frequency of 1567.98 Hz (Note: G6) directly to the stereo out without any hardware attached. Tried it with a grounded and a floating system (battery bank + laptop). Any further ideas? :D

where is the sketch, cannot find it.
 
The low pass filter had naturally to be implemented on digital level, directly after the stage which creates too much harmonics. And if later stages add new harmonics above the NyQuil then frequency, again a filter is required. No digital or analog signal block should be driven with signals which risk to generate aliasing.
Another point to take care of, is saturation (the digital equivalent to analog hard clipping), which gives similar ugly ghost tones. You’ll have to make sure that it won’t ever happen by doing worst case calculations.
 
where is the sketch, cannot find it.

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioSynthWaveform       waveform1;      //xy=459,120
AudioMixer4              mixer1;         //xy=630,140
AudioOutputAnalogStereo  dacs1;          //xy=790,140
AudioConnection          patchCord1(waveform1, 0, mixer1, 0);
AudioConnection          patchCord2(mixer1, 0, dacs1, 0);
AudioConnection          patchCord3(mixer1, 0, dacs1, 1);
// GUItool: end automatically generated code

void setup() {
  Serial.begin(9600);
  AudioMemory(10);
  
  mixer1.gain(0, 0.1);

  waveform1.begin(WAVEFORM_SAWTOOTH);
  waveform1.amplitude(0.5);
  waveform1.frequency(1567.98);
}

void loop() {
  Serial.println("Playing...");
}

@Theremingenieur Ok, got it but how?
 
The original poster is right in that if you generate a trivial square or sawtooth at the target sample rate, it has already aliased and is too late to filter it out.

An obvious solution is to generate at an oversampled rate, then downsample to the target rate. The problem is the harmonics of these waveforms diminish slowly with frequency, so the high oversampling required makes this a very expensive approach.

For specific waveforms used by subtractive synth, its possible to generate a bandlimited waveform to begin with. Search for minBLEP or polyBLEP. The basic idea is to do fixups that add Gibbs ringing at the discontinuities.

For arbitrary waveforms, you can do bandlimited wavetable synthesis. If the wavetable stores freqs up to Nyquist, you'll need expensive (with arbitrary cutoff) filtering to pitch it down without aliasing. One trick is to use multiple wavetables, prefiltered for each octave, to facilitate simple interpolation (2-tap linear, 4-tap Lagrange, etc). This is basically the same idea as MIP-maps for textures.
 
If you really wanted an exact sawtooth, or triangular or rectangular waveform, you hardly can generate it in ad sampled way, as you need very large bandwidth to generate the signals. Alternatively to using 'analog' simulating bandwidth, you could synthesise your sawtooth with a sum of sinusoids that are properly weighted (look up the Fourier coefficients in any math book) and limited to 1/2 sampling frequency.
 
Good point, you can sum the harmonics up to Nyquist to accurately synthesize these waveforms. When using wavetable synthesis, this is a great way to generate the bandlimited wavetables. But it can be expensive to do this real-time. You can use an inverse-FFT as an efficient oscillator-bank, but it gets ugly if you want to continuously modulate the pitch.
 
Using the sum of single sinosoids is not only a possible workaround for the aliasing of the available waveforms. Beside the weighting of the tones (set levels) to shape the sound it gives the extra opportunity to change each frequency to add some "analog character" by taste.
 
Hello folks!
I recently ran into the same aliasing problem while building a synthesizer. The standard sawtooth waveform is indeed strongly affected.
I'd ask, since @ohoomens built this really fancy sensei-synth, which apparently offers modulated waveforms (which are maybe even anti-aliased?)
While searching the web, i only found code for a bandlimited sawtooth, which uses the FAUST-language.
Is there any alternative or already code for alias-free waveforms somewhere around the teensy community? :)

thanks in advance :D
 
So, recently I have been experimenting with polyBLEPS a little bit.
I tried to implement the algorithm described in this blog: http://www.martin-finke.de/blog/articles/audio-plugins-018-polyblep-oscillator/ into the Audio library.
It hurts a little bit to contaminate the highly optimized Audio Library with float calculations, but the Teensy 4.0 is fast enough to hande these.


For anyone, who wants to try this out, here is what I've done:

I added a polyblep method to the top of the waveform.cpp file:

Code:
int16_t polyblep(uint32_t phase, uint32_t inc, int32_t magnitude){

	//0 <= t < 1
	if (phase < inc){
		float t = (float) phase/inc;
		return (int16_t) magnitude * (t*t - t+t - 1.0);
	}else{
		//-1 < t < 0
		if(phase > (4294967296 - inc) ){
			float t = (float) (phase - 4294967296) /inc;
			return (int16_t) magnitude * (t*t + t+t + 1.0);
		}else{
			return 0;
		}
	}
}

then, in AudioSynthWaveformModulated::update(void) (since I work with modulated oscillators on my synth), I modified the Square and Sawtooth waveforms:

Code:
case WAVEFORM_SQUARE:
		magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
			if (phasedata[i] & 0x80000000) {
				*bp = -magnitude15;
				*bp += polyblep(phasedata[i], inc, magnitude15);
				*bp++ -= polyblep(phasedata[i] + 0x80000000, inc, magnitude15);

			} else {
				*bp = +magnitude15;
				*bp += polyblep(phasedata[i], inc, magnitude15);
				*bp++ -= polyblep(phasedata[i] + 0x80000000, inc, magnitude15);
		       }
	        }
		break;


case WAVEFORM_SAWTOOTH:
	    magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
			*bp = signed_multiply_32x16t(magnitude, phasedata[i]);
			*bp++ -= polyblep(phasedata[i]+ 0x80000000, inc, magnitude15);

		}
		break;

It turns out, that the aliasing is reduced somewhat, but the algorithm also kills high harmonics.
So I will use it, when (lowpass-) filtering my signal while playing in high octaves. For lower octaves and less filtering, the benefit of the reduced aliasing is (in my opinion) not worth it, since the high harmonics are sometimes wanted (well, depends on your musical taste).
For anyone who wants to dig deeper into the Topic of polyBLEPS or anti-aliasing in general, the guys on kvraudio have lots of helpful threads there.
 
Last edited:
Status
Not open for further replies.
Back
Top