Please help extend my Polynomial Transition Region Sawtooth to an EPTR Sawtooth

Make Electronic Sound's icon

Hi,

I recently read the paper linked to this website:

I was able to make a PTR Saw. No problems.

I also read:

but I just can't get my head around how to adapt my attached PTR patch to use EPTR.

I think I would have to somehow customise the behaviour of the counter object? The EPTR Saw algorithm is very simple but I don't know how to apply it:

p=p+2*T; 
if(p>1-T){

y = p-(p/T)+(1/T)-1;
p=p-2;

}else{
y=p;
}

Can anybody help me out?

Dave

Polynomial-Transition-Region-Sawtooth.maxpat
Max Patch
bertrandfraysse's icon

This seem great, but I don't understand.
could you show the PTR saw ?
maybe with a starting point I could understand how to translate the others...

Make Electronic Sound's icon

Hi,

See the attachment!

Dave

bertrandfraysse's icon
monetus's icon

-wanted to say thanks for posting those pdf links. I'll give them thought.

sadguitarius's icon

I did this a while ago and haven't tested it much, so no guarantees about proper implementation etc., but basically your perform method would be something like this:

void saweptr_perform64(t_saweptr *x, t_object *dsp64, double **ins, long numins, double **outs, long numouts, long sampleframes, long flags, void *userparam)
{
t_double *outL = outs[0];
int n = sampleframes;
double inc = x->inc;
double phase = x->phase;

while (n--) {
if (inc != inc) {
*outL++ = phase;
} else {
phase += 2.0*inc;
if (phase > 1.0 - inc) {
     *outL++ = phase - (phase/inc) + (1.0/inc) - 1.0;
     phase -= 2.0;
} else {
     *outL++ = phase;
     }
}
x->phase = phase;
}
}

Make Electronic Sound's icon

@SADGUITARIUS Thanks, Ill take a look at whether or not that can help me in the Gen version.

sadguitarius's icon

@DAVEYC Whoops! Missed the patch you posted at the bottom. This is one of those things where I'm a little lost trying to patch in Gen and a C version makes way more sense to me but it shouldn't be hard to adapt the C version to a Gen codebox.

@RAJA_THE_RESIDENT_ASSWIPE If I remember right it's a trick to weed out NaN errors. The way I'd been calculating the phase increment could result in a divide-by-zero when the frequency was set to zero. NaN's fail the equality test so nothing happens in the perform routine.

Ernest's icon

Hi Davey, I wanted to offer some encouragement, first, using plot~ I got much better results for your sawtooth than for my LTR version. The difference didn't show up with the simpler spectral analysis object, but with a peopler Fourier with 1024 unwindwed samples, yours is definitely better, and better than the MSP object too below 50Hz (strangely, the cycle~ object also shows this distortion below 50Hz for some reason, but the noise below 50Hz doesn't appear if you use cycle with phase lookup in gen~. I tried to think of a rational explanation for the MSP noise at low frequencies, but so far Im at a blank).

Second, at least by Peter's definition, I think this is an EPTR, because it does only perform polynomial calculations during the transition region. Maybe I did something wrong when I tried to simplify the equations. Perhaps you could check it?

saw-eptr1.maxpat
Max Patch
Make Electronic Sound's icon

Hi Ernest.

It might well be EPTR. I'm not too sure. It sounds and looks pretty good. It would be nice to compare it with the PTR in terms of CPU usage. I could use Max 6 but it struck me I don't know how to see how much CPU a patch is using in Max 7. Any ideas?

Dave

Ernest's icon

I havent seen any difference between max 6 and max7. I think max7 patches open in max6 at the moment, unless you are using any of the new objects, which this doesnt. For cpu there is an example in the max help file for the gen~ object, on the profiling tab, which provides average usage over consecutive time periods. On Macs it takes into account parallel processing and on PCs it doesn't.

For eptr there isnt really a single simple measurement, because it has a much larger peak usage than average. Average usage will increase at higher frequencies when the transition window becomes a larger proportion of the audio cycles. Even so typically engineers provide average usage at 1khz for technical comparisons in most cases.

Ernest's icon

But with regarded to performance, I did take a deeper look at the pulse width polynomial, and in fact, it also is an EPTR by Peter's definition, and I dont know exactly where you got it, but at least half of it isn't a polynomial, it's simply a linear curve. P0 is samplerate/FC, and p is Fc/samplerate. So when you square or cube the two together, the result is always 1. Hence after including the scaling factors to get the output into the -1 to +1 range, I get this:

if(p
    if (p < inc) out1 = -1/3 + w*2;
    else if(p < inc * 2) out1 = 1/3 + w*2;
    else if(p < inc *3) out1 = 5/3 + w *2;
    else out1 = -1;
}

for the other half, one subtracts w from p to get the current phase increment in the transition region, which is more complicated.

    pw = p - w;
    if (pw < inc) out1 = pw*pw*pw*P0*P0*P0/3 + w *2 - 2;
    else if(pw < inc *2)
        out1 = -pw*pw*pw*P0*P0*P0/3 + 3*pw*pw*P0*P0 - 3*pw*P0 + w*2 - 1;
    else if(pw < inc * 3)
        out1 = pw*pw*pw*P0*P0*P0/3 - 3*pw*pw*P0*P0 + 9*pw*P0 + w*2 - 9;
    else out1 = 1;

I think it would be possible to simplify it, but it's been 35 years since I ever simplified a factorial, so I leave that thought to you. but it does explain why the noise reduction on Peter's tanh method is better, because it provides a curved response over the transition period, instead of linear as in the above.

Make Electronic Sound's icon

Hi Ernest,

Food for thought for sure.

I'm very busy with my day job right now but I appreciate your input and I'll definitely look into optimising the pulse polynomial as soon as I get the chance, probably next week sometime and post any results. Have a good weekend.

Ernest's icon

Wups, I think I made a mistake that inc is a fraction which should be included. But I am sure it can be simplified like I am trying to, and I think there is another problem that w should not be included in the first half:

if(p
    if (p < inc)
        out1 = 2 *(inc*inc*inc/6 + w);
    else if(p < inc. * 2)
        out1 = 2 * (2*inc*inc*inc/6 - 3*inc*inc/2 + 1.5*inc +w - 0.5);
    else if(p < inc *3)
        out1 = 2 * (-inc*inc*inc/6 + 3*inc*inc/2 - 4.5*inc + w + 3.5);
    else out1 = -1;

I kind of reached my limit on this stuff too :)

Make Electronic Sound's icon

Hi. Took a quick look at this today but couldn't really see how to optimise it - for now. I'm sure there may well be a way to simplify the code but for now I'm not too worried about it.

Cheers

Make Electronic Sound's icon

I dont know exactly where you got it

Directly from here:

Ernest's icon

thats very interesting.

I was surprised to discover bit-wise operations are not available in gen~, unless they are undocumented.

Peter McCulloch's icon

I think that's because all values are represented as doubles, so even if you get the int value of a number, it's still a double internally.

The other problem with allowing users to do bitwise manipulation is that it ties it to the implementation so if the implementation changes, your code no longer works. You can, of course, still do these types of things in your own C/C++ code... This is all just supposition on my part, though.

Ernest's icon

Hi, I finally figured out how to improve this EPTR. As one knows the waveshape, one can forward extrapolate and center the upsample window. Tada )