Roland Supersaw - any idea how the original was done?

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

hugoderwolf wrote: Thu Jan 22, 2026 8:14 am Indeed with integer wavelength you get no inharmonic aliasing, but the harmonic amplitudes are messed up. I'm not sure why because in my mind a BLEP would have its zero crossings at integer sample offsets. But that's probably simply not the case (didn't confirm).
Harmonic amplitudes are messed up 'cos the aliasing never went anywhere, it just happens to fall exactly at the same frequencies as the regular harmonics.

BLEPs don't put zero-crossings (or more generally: transitions) at integer samples. You solve for the exact transition that happens somewhere within the sampling interval and then you shift (or choose a tabulated branch that is suitably shifted) the BLEP by matching amount.
But it could be also an interesting approach to quantize to integer wavelengths instead, as the harmonic amplitude problem might be tolerable when layering 7 saws. Then you'd save the highpass as there are no harmonics below fundamental. However, tuning of higher notes will be off (haven't checked by how much).
Try it. You'll find that pitch accuracy becomes next to useless for actually making tonal music.

Post

Here you go:

Code: Select all

import numpy as np
import matplotlib.pyplot as plt


fs = 48000.0
f = 440.0

floatWaveLength = fs / f
integerWaveLength = int(np.round(fs / f))

N = int(fs)

floatSaw = np.zeros(N)
intSaw   = np.zeros(N)

floatSawPhase = -0.5
intSawPhase = -0.5
intCounter = 0

for k in range(N):
    floatSaw[k] = floatSawPhase
    intSaw[k] = intSawPhase

    floatSawPhase += 1.0 / floatWaveLength
    intSawPhase += 1.0 / integerWaveLength
    intCounter += 1

    if floatSawPhase >= 0.5:
        floatSawPhase -= 1.0
    if intCounter >= integerWaveLength:
        intSawPhase = -0.5
        intCounter = 0

win = np.hanning(N)

floatSpectrum = np.fft.rfft(floatSaw*win) / N
intSpectrum = np.fft.rfft(intSaw*win) / N
freqs = np.fft.rfftfreq(len(floatSaw), 1/fs)

def db(x):
    return 20 * np.log10(np.maximum(np.abs(x), 1e-10))

plt.figure(figsize=(10, 6))
plt.semilogx(freqs, db(floatSpectrum), label='Float Wave Length Spectrum', color='blue')
plt.semilogx(freqs, db(intSpectrum), label='Integer Wave Length Spectrum', color='orange')
plt.title('Frequency Spectrum of Sawtooth Waves')
plt.xlabel('Frequency (Hz)')
plt.ylabel('Magnitude (dB)')
plt.xlim(20, fs/2)
plt.ylim(-100, 0)
plt.legend()
plt.grid()
plt.show()
May contain traces of AI slop and some handwavy inaccuracies. Just found and fixed an error in the dB calculation (it took the real part instead of the abs, can you believe that?). The integer one is actually even closer:
NaiveSaw_FloatVsInt.png
You do not have the required permissions to view the files attached to this post.

Post

mystran wrote: Thu Jan 22, 2026 9:51 am Try it. You'll find that pitch accuracy becomes next to useless for actually making tonal music.
I tried it once at 16x oversampling and it was still utterly useless. Once it does become useful I guess it's a lot easier and less CPU intense to just insert a damn BLEP.

Post

mystran wrote: Thu Jan 22, 2026 9:51 am BLEPs don't put zero-crossings (or more generally: transitions) at integer samples. You solve for the exact transition that happens somewhere within the sampling interval and then you shift (or choose a tabulated branch that is suitably shifted) the BLEP by matching amount.
The point of a BLEP is basically to insert a bandlimited step with subsample accuracy (at/around fractional sample positions). I assumed from memory that the difference between the BLEP and the naive step has zero crossings at integer sample distances from the actual step position. So if the fractional part of the BLEP position is zero, the actual values inserted are also all zero. Seems that might not be actually true, but I don't have a BLEP at hand quickly right now to check.

Post

Urs wrote: Thu Jan 22, 2026 11:05 am I tried it once at 16x oversampling and it was still utterly useless. Once it does become useful I guess it's a lot easier and less CPU intense to just insert a damn BLEP.
Thanks for clarifying! I vaguely remember having made that experiment a long time ago, and in my idealized memory it was somehow on the "maybe marginally acceptable if you really don't care much and stay away from piccolo flute pitches". So I thought maybe as part of a detuned cluster it might be "ok" or "have character". :ud:

Post

What would it sound like if we would apply a sort of probabilistic "pitch-jittering" or "pitch-dithering" approach as follows: When the desired pitch says that the cycle length should be 100.3 samples, produce cycles of length 100 with probability p = 0.7 and cycles of length 101 with probability p = 0.3? Has anyone tried something like that? Does that make any sense? It's just a wild idea that just came to my mind.

Or maybe it shouldn't be probabilistic but instead we could keep track of the balance produced so far to determine deterministically whether or not the length 100 or the length 101 has its turn? I guess, doing it like this could produce subharmonics? If the desired length should be 100.5 and we alternatingly produce cycles of length 100 and 101, we might see a suboctave or something like that? A sequence of two cycles would always (deterministically) have length 201 (as desired) and the two cycles would differ slightly (in length) thereby producing a little bit of a suboctave signal.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

Ha, I actually tried that a while back, coined it "Time Domain Dithering". It "works", as in: concentrated energy of aliasing frequencies is effectively spread over the whole frequency range. But the resulting noise level is too high in order to be useful. SNR decreases significantly towards higher pitches.

Edit: again this is recalled from my sometimes very inaccurate memory.

Post

hugoderwolf wrote: Thu Jan 22, 2026 11:10 am
mystran wrote: Thu Jan 22, 2026 9:51 am BLEPs don't put zero-crossings (or more generally: transitions) at integer samples. You solve for the exact transition that happens somewhere within the sampling interval and then you shift (or choose a tabulated branch that is suitably shifted) the BLEP by matching amount.
The point of a BLEP is basically to insert a bandlimited step with subsample accuracy (at/around fractional sample positions). I assumed from memory that the difference between the BLEP and the naive step has zero crossings at integer sample distances from the actual step position. So if the fractional part of the BLEP position is zero, the actual values inserted are also all zero. Seems that might not be actually true, but I don't have a BLEP at hand quickly right now to check.
It's actually rather the other way around. The key is to think about what the reconstruction looks like. You can think of the naive waveform as one where the discontinuity has been snapped to half-way between sampling instants (that's not perhaps completely accurate, but good enough for purposes of understanding what is going on), so the waveform is no longer truly periodic. When we insert a BLEP, what we are doing is inserting the necessary filter ringing to move the discontinuity back to where it should be and restores the periodicity.

Post

Music Engineer wrote: Thu Jan 22, 2026 11:34 am What would it sound like if we would apply a sort of probabilistic "pitch-jittering" or "pitch-dithering" approach as follows: When the desired pitch says that the cycle length should be 100.3 samples, produce cycles of length 100 with probability p = 0.7 and cycles of length 101 with probability p = 0.3? Has anyone tried something like that? Does that make any sense? It's just a wild idea that just came to my mind.
I haven't tried this one in particular, but in general dithering in time is a thing and (when done correctly) can turn any aliasing into broadband noise (just don't expect it to be more than a little bit below where the aliasing would have been). "Non-uniform alias-free sampling" in particular should be a helpful search term (with "additive random sampling" probably the most straight-forward method that actually works to get rid of aliasing completely; the trouble with these though is that actually resampling back into uniform time is "challenging").

Post

mystran wrote: Thu Jan 22, 2026 12:27 pm I haven't tried this one in particular, but in general dithering in time is a thing and (when done correctly) can turn any aliasing into broadband noise (just don't expect it to be more than a little bit below where the aliasing would have been). "Non-uniform alias-free sampling" in particular should be a helpful search term (with "additive random sampling" probably the most straight-forward method that actually works to get rid of aliasing completely; the trouble with these though is that actually resampling back into uniform time is "challenging").
Yes - this is what I would have expected. The aliased sinusoids are gone at the expense of introducing a noise floor:
AliasingVsNoiseScaled.png
The figure is from this paper:

https://www.researchgate.net/publicatio ... AL_CONTROL

Thanks for the search keywords. Yes - I see that in general converting back from a non-uniform to a uniform sample rate could be challenging. Could we just place scaled (windowed) sincs over the samples and sum them up? I think, that might be the first thing I would try - well, maybe the second thing after naive linear interpolation (or maybe polynomial - probably Hermite with target values for the derivatives from a numerical differentiation routine (because I happen to have one for non-uniform data, as you may remember from another discussion)). But in this particular case here, the problem would not even arise.
You do not have the required permissions to view the files attached to this post.
Last edited by Music Engineer on Thu Jan 22, 2026 6:43 pm, edited 2 times in total.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

hugoderwolf wrote: Thu Jan 22, 2026 12:00 pm Ha, I actually tried that a while back, coined it "Time Domain Dithering". It "works", as in: concentrated energy of aliasing frequencies is effectively spread over the whole frequency range. But the resulting noise level is too high in order to be useful. SNR decreases significantly towards higher pitches.
Did you use any amount of oversampling? If not, maybe with a mild amount (like 2x or 3x), the noise could be reduced to an acceptable level? And how does the noise sound like? I think, I would expect some sort of "growliness" being introduced into the sound because pitch cycles being randomly different from one cycle to another tends to produce growl, in my experience. ...but I agree with Urs that it's probably better to just insert the damn BLEP, if anti-aliasing is the goal. It's just an interesting thing to ponder from an academic perspective. And replacing aliasing with growling actually sounds like a good deal. Aliasing kinda sounds universally bad but growling can actually sound good.
My website: rs-met.com, My presences on: YouTube, GitHub, Facebook

Post

Music Engineer wrote:What would it sound like if we would apply a sort of probabilistic "pitch-jittering" or "pitch-dithering" approach
You can listen to it in a video that demonstrates the sound of the Sequential Prophet VS:

The Story of the Prophet VS

From 3:18 on the shortcomings of that approach are demonstrated. Nowadays coveted digital artifacts.

Post

Music Engineer wrote: Thu Jan 22, 2026 6:33 pmCould we just place scaled (windowed) sincs over the samples and sum them up?
Well.. you CAN do it, but it's not actually the correct solution for reconstruction.

In the general case, the ideal reconstruction involves fitting a Lagrange-polynomial through all the points. In the case where the sampling intervals are uniform, this converges (slowly!) to sinc-interpolation, but that doesn't work in the non-uniform case.

You can do it, but it will be awfully noisy. You can fix the noise (from the reconstruction; this won't remove other sources of noise such as from aliasing) approaching DC by using zero-order hold with BLEPs, but due to "sinc-distortion" this doesn't really help at high frequencies (although this distortion is relative to the random sampling intervals, so you can make it less of an issue by increasing the average sampling rate).

Post

odibo wrote: Thu Jan 22, 2026 10:44 pm From 3:18 on the shortcomings of that approach are demonstrated. Nowadays coveted digital artifacts.
There seems to be quite obvious aliasing here, so whatever is being done isn't really getting rid of it properly.

Post

jupiter8 wrote:You just don't use hipass filters for AA.
It's not exactly AA but psychoacoustics.

If the inharmonic aliased frequencies are somewhere between non-aliased harmonics, typically at lower levels, they are masked, i.e. kind of inaudible to humans. Their level can be quite high without being objectionable.

On the other hand, inharmonic aliased frequencies below the fundamental won't be masked, since there are no other frequencies around that could do that. A tracking high pass filter can mitigate that.

The higher your oscillator frequency gets, the wider the harmonics will be apart, and masking will become less effective. The psychoacoustics trick falls apart if you run your audio through some filter with high resonance, which can make those aliased frequencies quite objectionable. But who cares, if it sounds interesting?

Speaking of which. The mostly inaudible aliasing in the upper frequency region brings a pleasant, powerful energy to the sound which properly antialised sawtooth oscillators don't have. If your ears are old, you will hear that only to a lesser extent.

Post Reply

Return to “DSP and Plugin Development”