Upsample to reduce aliasing using only Gen, not poly~ object.
Hi,
I have a triangle wave with variable slope. It is currently not bandlimited. I know I can use poly~ like in the attached example but I want a simple explanation, or better still please edit this patch, of how I can upsample, filter to Nyquist and downsample all in Gen.
The link below describes a Pure Data patch which does 16x upsampling. How can I achieve this in Gen?
Any help is appreciated.
Dave
hi.
i have bad news for you.
there is no simple resampling in gen~. you have to roll your own. simple resampling is a known feature request for gen~ and hopefully on their agenda.
i made a sinc resampling system for arbitrary up/down in gen~ but it was quite useless as it took way too much cpu.
the simplest is the cheap trick interpolation method - repeat the processing x # of times, then high-quality interpolate between them. this is quite easy in gen~ for 4x because of the [history] and [interp] operators, and is the same method used often throughout reaktor-core.
however, a couple of points:
- usually one would resample based on the code at hand - i.e., not brute force but various methods whether filters or oscillators or waveshaping, etc
- the well known pd examples you posted are not really resampling. they are even worse than pre-max-6 days. in fact they are so terrible it is sort of embarrassing they form a tutorial. an x-pole butterworth lowpass at the end of your x-times chain for a naive oscillator is never really going to do much good.
- a lot of those pd people and those finish guys and the likes of lazarini et al often work with 'alias suppression' for oscillators, but you got to ask yourself, why work with shit which needs 'suppression' in the first place? that is why people used to do 'blit' and now do 'blep' and 'blamp'.
so, none of that is supposed to be disheartening (or rude!). just my opinions, please ignore if you wish.
however, you'd be better off using the gen~ BLIT examples in the examples folder to make triangles etc, which is actually relatively simple. there was a post about it a while back.
for the record, yes it would be fantastic to have arbitrary resampling system inside gen~, and i too hope for it one day.
.
Hi STKR,
Thanks for replying.
however, you’d be better off using the gen~ BLIT examples in the examples folder to make triangles etc, which is actually relatively simple. there was a post about it a while back.
I wasn't able to find a way to adapt the BLIT saw example into a triangle. In Digital Sound Generation: http://courses.cs.washington.edu/courses/cse490s/11au/Readings/Digital_Sound_Generation_1.pdf they have a diagram for DSF-BLIT Sawtooth Oscillator but no Triangle. I also couldn't find an example online or work out how to adapt the Gen codebox code for the saw. There are, however many other Triangle algorithms in the same document but I was unable to implement any of them in Gen. I'll make a separate forum question tonight explaining this.
EDIT: I made a triangle wave by using a leaky integrator on the end of my PTR Square wave. Happy days
the simplest is the cheap trick interpolation method – repeat the processing x # of times, then high-quality interpolate between them. this is quite easy in gen~ for 4x because of the [history] and [interp] operators, and is the same method used often throughout reaktor-core.
I tried to use the History
object and Interp
to produce less aliasing but it doesn't work. Unless I have completely misunderstood what you meant, it should be possible to reduce aliasing using your suggestion? I realise that a better solution is to use another method for my triangle oscillator but I'd like to know how (if possible) to reduce aliasing by Interpolation. Perhaps I've completely got the wrong idea!! Anyway, now I made my bandlimited triangle I don't care but by all means examine my patch if you wish.
Cheers
Dave
hi. no time to look at your stuff now, sorry. hopefully these help.
attached is a dsf-blit multiwave example. pasted below is a resampling example.
hth.
That's awesome. Thanks very much
Hi Raja,
Yes, I settled for a triangle derived from a PTR square I made. Very low cpu usage. I was still interested in how the upsampling would work however.
I'd be interested in looking at this triangle and square wave you made, as the saw of the other post is really great...
@BERTRANDFRAYSSE Sure. No problem. Here you go. I'll also share this as a tool.
The duty cycle messes with the amplitude of the triangle (partially solved in my patch) and moves the square but these problems should be easily solved.
What would be really fantastic would be if somebody could take the saw and square here and adapt them from PTR to EPTR. That would be great. SADGUITARIUS provided some C code but I haven't tried to implement it yet. Then the oscillators would be even more efficient. Would be a nice Christmas present perhaps from somebody in the know to the rest of us:
Enjoy :-)
That's some nice coding work, thank you. I did implement 3x upsampling in gen~ objects for an SVF filter, if you're interested, the link to it is here
Hi Ernest,
No problem!
I'll take a look at your link. Thanks very much
Dave
The ICST library has a triangle oscillator that is simpler and just replaces the transition with a polynomial. It's more efficient than most methods, IIRC.
Thank you Peter, very helpful. I think I have a better idea for upsampling a ramp signal source for oscillators. Spline interpolation tries to guess the signal shape but introduces a discontinuity for a ramp causing jaggies in sines and other nasties. So I built this, which simply interpolates values linearly while the ramp is rising, and, iff the ramp ended in the last cycle, interpolates the slope from the prior sample and applies wrapping on each upsample phase so the ramp downslope occurs in the optimal place. Maybe there is a better way to do things, I am still learning.
Hi Ernest, this looks somewhat like the PTR algorithm; you might check out the paper for that if you haven't already. It's similar: the wave is unaffected through most of the ramp, and the interpolation switches on at the discontinuity. HTH
Hi Peter,
Thanks for the info.
Dave
Well, after fixing it, I was still unimpressed by the quality of the output compared to your oscillators. After downsampling with both spline and sinc+blackman window, even with 16x oversampling, there was only a slight reduction in noise at higher frequencies. I think both Davey and Peter have a better existing approach than with upsampling, unless Im missing something, but I had read before that was about as much as one can expect unless one does no downsampling.
Sawtooth wave harmonics fall off at 1/f in terms of amplitude, so an oversampling+downsampling approach usually doesn't work well because there are just so many partials.
I definitely recommend the ICST "Digital Sound Generation" pdfs because they explain a lot of these issues and also provide suggestions as to which algorithms are appropriate for particular conditions. For example, the triangle wave I mentioned is good if you need an efficient triangle, but it can't do things like the variable slope present in tri~'s middle inlet.
Well I was still expecting better results, but after sleeping on it, I realized this is not really upsamplikng per se. It is running four oscillators at a slight offset from each other at the same frequency, and then mixing the results to emulate upsampling. The oscillators are still running at the same frequency, not higher, so the Nyquist is exactly the same, repeated four times then mixed down. Thus a little noise reduction is exactly what should be expected. Upsampling by this method doesn't really change the aliasing at all.
Maybe the approach for varying triangle/saw is to run two ramp oscillators. Suppose the 'triangle' has a 75% rising and 25% falling slope. then could two antialiased saws make it by running one at 4x frequency and one at 1/.75 frequency with inverted phase, and switching between them at ramp boundaries?
Well I have to agree, on investigation, blep fidlling seems far more computationally intensive and difficult to get right than PTR and EPTR. There is a paper on an EPTR algorithm here which does describe an EPTR method for a varying triangle waveform. The description is clear enough that a low-level mathematician like me can still understand it...so I will try it out.
@ERNEST If you manage to implement EPTR in Gen, please share your results. I tried but failed, see:
Cheers
Dave
But of course. It is worth considering that maybe EPTR doesn't work as well as touted, after all, oversampling by duplication doesn't help with Nyquist and a lot of people do it anyway. Thinking about it, it seems like it is only selective application of a low-pass filter to the problematic portion of the wave, so I don't expect as good results as for BLEPs, but it is certainly, by first appearance, a lot easier to do.
First I made the antialiased saw with PTR, attached, and right away I can tell you there is a mistake at the beginning of this paper which may explain your problems. The equation for samples before the discontinuity
y[n] = p0 - p0/T + 1/T -1
should be
y[n] = p0 - p0*T + T -1
As obviously p0/T when p0>0 is much greater than 1. However, please let me add, I am not really a mathematician, I only have a lot of experience with audio. As a corolarry, I do make the observation, when p0 < 1 (that is the half way point between two samples when the next sample is the saw discontinuity is before the discontinuity), an approximation of 0 is still pretty good, which halves the calculations. Anyway I do see the PTR saw seems to work very well, here is the first maxpat.
I made the same mistake with calculating the intermediate sample as the first time with downsampling, AND it is simpler to subtract delta from the ramp than add half to history +delta to the multiplied value, AND I removed another subtraction and multiplication from the genexpr by making use of the phasor already providing values in the range 0-1 which are half those of the output. New maxpat attached.
Now for the EPTR algorithm described in this paper, I don't see it providing significant enhancement over my second maxpat. This is because it requires no wrapping on the phasor signal, but to let the accumulator free run. I think This is impractical because the increment is small. Hence after time the accumulator value will be very large and even with 64-bit floating-point, the increment value will be too small to appear in the output.
One possibility would be to accumulate freq rather than freq/samplerate, then divide by samplerate afterwards. Even with that, after a time the increment wouldn't show up in they floating point value output by the accumulator, but maybe the time before that error would be so long it doesn't matter, I'm not sure.
Finally I did a test with the saw PTR quotients in this article against simply substituting the top and bottom transition points with 1 and -1. It's several times more efficient, and the I can't see any discriminable difference in the results. Maybe I set the quotients wrong. Anyway here it is, as per request.
Hi Ernest.
Thanks very much. On holiday without my machine right now but will download when I get back in about a week.
All the best
Dave
Hope you have a great vacation. I tidied it up quite a bit, added FM and sync, and a Yamaha-style multisine oscillator. I think it wouldn't be a big step to add full polynomials and antialias higher octaves too, as the transition points are all isolated. I'll try to make an EPTR version of PM's PTR pulse.
And I am very pleased with the results! it yields an astonishingly pure tone, as you can see from the attached illustration, and provides effective antialiasing up to C7. It sounds much more like an analog oscillator than anything I have heard in a while, and at first I thought it a little thin, and then realized how accustomed I had become to alias distortion. So first, here is the code.
History z0; History z1; History z2; History z3; History z4;
fc = in1; // freq (Hz)
inc = in2; // phase increment(including FM), Fc/2*sr
sync = in3; // resets phase on positive transitions
w1 = .5 * in4; // Duty cycle. 0.025 = 5%, and 0.5 = 95%.
w2 = (1-in4 *.5); // inverted duty cycle
sr2 = in5; // 2/SR constant (restart required to change sr).
// The phase accumulator works in the range -1~+1,
// to prevent phase inversion by negative wraps from FM signals
ramp = (sync)? 0: wrap(inc + z0,-1,1);
z0 = ramp;
// In case FM present, a new incr is interpolated from phase history
z1=inc; z2=z1; z3=z2; z4=z3;
inc2 = interp(inc,z1,z2,z3,z4,mode="spline");
/* --- Pulse with EPTR antialiasing --- */
ramp2 = ramp *.5 + .5; // ramp rescaled to 0-1 for EPTR calcs
d = 2 * inc2; // width of phase transition region, 4*fc/sr
d2 = 0.213332 / d; // increment factor in transtiion region
// the Pulse is divided into five stages. Antialiasing calucations
//only occur during the fisign and falling transionts, resulting in
// extremely low CPU usage.
if (ramp2
The only thing I am really not sure about is how interpolation works in the spline object. So I will do some checking on it, and upload the demo patch to Yofiel.
Great work Ernest. Looking forward to checking this out.
I think I finished the documentation today, and on discussion with some people in musicdsp.org I think I have an additional enhancement for FM sync. Currently there is still a bit of a problem if sync falls during the transition phase.
Over there, they call this a 'polyblep' and some people have done similar things, but not as far as I can tell using the sin/tanh shaping like Peter shared, which appears to be much better for FM. Hackles rose a bit when I said there was good precedent for this to be called 'EPTR,' so you are warned.
Regardless the sync issue, I am totally delighted with this design, I never thought I would be able to do it. Thank you Davey and Peter for your help, and happy thanksgiving.
No worries Ernest. I downloaded the patches from your site. Looks good.
Happy thanksgiving to you too.
oh, well I added a lot of explanation. Also I didn't see this before....there is a help patch for plot~, on the spectral tab, that gives much better frequency analysis )
Hi Ernest.
Yes, I see that you've been busy. It looks like a good read but it's near 2am here and I'm currently struggling with an unrelated issue. --> just in case you know the answer ;-) .......... Playing back a wav in Gen without using Buffer (using Data only - https://cycling74.com/forums/buffer-and-data-dont-sound-the-same/)
Anyway, well done again and I'll be sure to have a closer look at your website asap.
Cheers
Dave
Looks like you are having fun with alot of other bits. I think you also said you wanted a wavetable oscillator, well I got a waveset oscillator working in gen~ which you may find helpful:
I cant remember whether it was wabetable or waveset, but the same method pretty much works for both
Hello Ernest! Nice to hear from you.
I'll check that out. I'm very interested.
All the best, Dave