Returns periodic band-limited square, triangle, or sawtooth waveform with period 2π
Meant to be called the same way as scipy.signal.waveforms.square
,
but without the aliasing effects produced by the existing "naïve"
implementation. Main goal is numerical accuracy for doing simulations,
so the wave is generated by additive synthesis.
At low frequencies, the difference from the naive version is minimal, and the computational cost of the additive synthesis is high.
At high frequencies, the difference is very obvious, and the computational cost of additive synthesis is not too bad.
It currently requires a constant sampling frequency (no chirps) and doesn't support width or duty cycle.
Usage:
t = linspace(0, 1, num = 1000, endpoint = False)
plot(bl_square(2 * pi * 3 * t))
Band-limited version in blue, naïve version in red (equivalent to sampling the ideal mathematical function without putting it through an antialiasing filter first)
The extra spikes are the aliased harmonics, which are even more obvious when log-scaled:
Sound examples:
- https://soundcloud.com/endolith/duty-cycle-sweep-aliased-vs-bandlimited-1
- https://soundcloud.com/endolith/weierstrass-sin-a-099-b-2
TODO:
Something like BLEPs might be better for this? (Probably not; they are not as accurate due to truncation of the sinc
.)
Or the discrete summation methods would be more precise? Perhaps, but they cannot generate sawtooth/triangle/square directly, because the harmonic fall-off is at a different rate. They can generate BLITs, which can then be integrated to form the others, but this accumulates numerical error and requires leaky integration. Not sure how accurate that is.
With sweeps or chirps, there is also the problem of harmonics just disappearing suddenly when they hit fs/2 and producing sudden amplitude changes, which can be fixed with a roll-off filter, but which roll-off filter? It should probably be a parameter? But how to specify it as a parameter? I could also just cut it off suddenly, and let the user do their own filter, or oversample and decimate, if they want to. That's probably best.
No, you can't cut off the sweep frequencies so that one sample has a frequency and the next does not and then filter after the fact. The transition will cause a glitch with infinite harmonics that will alias, and post-filtering won't help. So it needs some kind of filter=
argument for specifying the roll-off filter. Would it specify a filter object? and then harmonics are scaled by its frequency response? And filter=None
should be possible which represents a brickwall! Then it will click for sweeps or go right up to Nyquist for non-sweeps if that's what's desired.
Also should have a phase={'zero', 'minimum', 'filter'}
option.
scipy/scipy#1507 scipy.signal waveform generators are not bandlimited (Trac #980) #1507