triangle & sawtooth oscillators : how to deal with aliasing ?

Status
Not open for further replies.
Triangle waves don't behave as badly as the discontinuity is 2nd-order, pulse type I've not looked at. It may be an easy
generalization of square wave or not, hopefully the former.
 
Yes, triangles sound fine. The variable triangle wave can be varied to a sawtooth and reverse sawtooth, I imagine trying to band-limit at just those points is a pain. The square and sawtooth are a fantastic improvement that people have been requesting for several years.
 
And the latest changes to the branch support WAVEFORM_BANDLIMIT_PULSE !

My sanity was restored by finding an online midi file of Toccata and Fugue in D-minor to replace the William Tell score array
in the PlaySynthMusic example BTW!
 
Great! I'll test. Yes, I was reminded of Walter/Wendy Carlos and Clockwork Orange while listening to it...
 
Released another change, sets the WAVEFORM_BANDWIDTH_PULSE DC offset to match the duty cycle to greatly reduce pumping effect on note attach/release.
It might cause issues with rapidly modulated pulse widths though as its not sophisticated! Compare it to the original PULSE to see what I mean.

Because of the shift in offset the amplitude necessarily has to be a lot lower than the existing PULSE waveform.
 
Hello Mark. Square and sawtooth are great, the pulse wave is fine until there's any sort of modulation, even around 0.5Hz. There's digital noise that isn't regular but often. Can it be improved? Many thanks for all your work on the Audio Library.

Manual adjustment and then lfo:
pulsewave.wav




Code:
// demonstrate pulse with slow changes in pulse width

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
const float NOTEFREQS[128] = {8.176, 8.662, 9.177, 9.723, 10.301, 10.913, 11.562, 12.25, 12.978, 13.75, 14.568, 15.434, 16.352, 17.324, 18.354, 19.445, 20.602, 21.827, 23.125, 24.5, 25.957, 27.5, 29.135, 30.868, 32.703, 34.648, 36.708, 38.891, 41.203, 43.654, 46.249, 48.999, 51.913, 55, 58.27, 61.735, 65.406, 69.296, 73.416, 77.782, 82.407, 87.307, 92.499, 97.999, 103.826, 110, 116.541, 123.471, 130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220, 233.082, 246.942, 261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440, 466.164, 493.883, 523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880, 932.328, 987.767, 1046.502, 1108.731, 1174.659, 1244.508, 1318.51, 1396.913, 1479.978, 1567.982, 1661.219, 1760, 1864.655, 1975.533, 2093.005, 2217.461, 2349.318, 2489.016, 2637.02, 2793.826, 2959.955, 3135.963, 3322.438, 3520, 3729.31, 3951.066, 4186.009, 4434.922, 4698.636, 4978.032, 5274.041, 5587.652, 5919.911, 6271.927, 6644.875, 7040, 7458.62, 7902.133, 8372.018, 8869.844, 9397.273, 9956.063, 10548.08, 11175.3, 11839.82, 12543.85};

// GUItool: begin automatically generated code
AudioOutputUSB           usbAudio;       //xy=2356,593
AudioSynthWaveformModulated       waveform1;      //xy=188,240
AudioSynthWaveform       pwmWave;      //xy=188,240
AudioEffectEnvelope      envelope1;      //xy=371,237
AudioOutputI2S           i2s1;           //xy=565,241
AudioConnection          patchCord1(pwmWave, 0, waveform1, 1);
AudioConnection          patchCord6t(waveform1, envelope1);
AudioConnection          patchCord2(envelope1, 0, i2s1, 0);
AudioConnection          patchCord3(envelope1, 0, i2s1, 1);
AudioConnection          patchCord4(envelope1, 0, usbAudio, 0);
AudioConnection          patchCord5(envelope1, 0, usbAudio, 1);
AudioControlSGTL5000     audioShield;     //xy=586,175
// GUItool: end automatically generated code


void setup(void) {
  // Set up
  AudioMemory(8);
  audioShield.enable();
  audioShield.volume(0.40);

  pwmWave.begin(0.8f, 0.5f, WAVEFORM_SINE);
  // waveform1.begin(0.4, 220, WAVEFORM_PULSE);
  waveform1.begin(0.4, 220, WAVEFORM_BANDLIMIT_PULSE);

  envelope1.attack(50);
  envelope1.decay(50);
  envelope1.release(250);

  usbMIDI.setHandleNoteOff(myNoteOff);
  usbMIDI.setHandleNoteOn(myNoteOn);
}

void myNoteOn(byte channel, byte note, byte velocity) {
  waveform1.frequency(NOTEFREQS[note]);
  envelope1.noteOn();
}

void myNoteOff(byte channel, byte note, byte velocity) {
  envelope1.noteOff();
}
void loop() {
  usbMIDI.read(); //USB Client MIDI
}
 
Hello Mark. Square and sawtooth are great, the pulse wave is fine until there's any sort of modulation, even around 0.5Hz. There's digital noise that isn't regular but often. Can it be improved? Many thanks for all your work on the Audio Library.

Thanks for testing - and I've reworked the pulse code to be much better - it should have fixed modulation issues and I've
improved the generality by allows arbitrarily fine pulses and fixed the DC-offset compensation to actually be scaled
correctly (so the modulation frequency shouldn't generate a hum component, upto a point).

The pulse shape modulation is now sampled at the rising edge of the pulse waveform so its no longer a moving target while
processing samples (its hard enough when just the phase increment is arbitrary!). I've not run your code but have been
using simultaneous FM and pulse modulation a fair bit with pulse modulation at full amplitude.

[ Scope shot: pulse_waveform_mod.png ]
 
Last edited:
Had a play with my spectrum analyzer today (it rolls off below 9kHz so not great for audio, but has a resolution bw down to 1Hz so it can
pick up a lot).

Audio lib's standard sawtooth spectrum:
raw_saw.png

And my band-limited version:
bandlimit_saw.png

Which shows the power of a bandlimited step of reasonable resolution...
The cutoff is below the SGTL5000's in-built filtering point, and much stronger,
completely knocking out the 5th harmonic at 20kHz

I must try this out on flo's PolyBLOB too.
 
Has this bandlimited version been incorporated yet?
If so, how might one get it installed?
(i'm new to this, so forgive naive question.)

thanks,
tim.
 
Thanks MarkT, for the information.
It looks like the difference is just 4 files (3 changed files and one new file).

data_bandlimit_step.c
keywords.txt
synth_waveform.cpp
synth_waveform.h

I can just put these in ..Arduino\hardware\teensy\avr\libraries\Audio
changed my project to use WAVEFORM_BANDLIMIT_SAWTOOTH, instead of WAVEFORM_SAWTOOTH.
Restarted arduino app, then rebuilt the project, right?

The arduino environment is new to me, so I'm not sure if there is anything else I'm supposed to do.

UPDATE:
I tried this out and the new code seems to work great! (WAVEFORM_BANDLIMIT_SAWTOOTH anyway--haven't tried the others yet). Way better than the original--no more aliasing.

Thanks for that.

Thanks
 
Last edited:
Another way, which might be better for procedurally generated waveforms, is oversampling. Say you do 4x oversampling, you use 1/4 the phase increment you normally would, but calculate the oscillator function 4 times, each time adding the result to an accumulator. for the final output sample, divide the accumulated result by 4 which averages the 4 oversamples.

notice the "4" in 4x oversampling is used everywhere, it can be a variable controlled by a button or potentiometer if you need to test it on the fly (good to hear immediate change on listening test!) you could even get fancy and have the oversampling decrease as cpu load increases due to polyphony and/or unison.

each 2x you oversample raises the point where aliasing becomes noticeable about an octave higher.

Depending on the complexity of the oscillator function, the teensy 4.x cache can help a lot here.

Oversampling can help with wavetables as well, but fetching the wavetable data repeatedly causes a performance hit, especially at higher frequencies and with large wavetables.
 
Status
Not open for further replies.
Back
Top