[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [sc-users] Hardsynced squarewave and anti aliasing technics



For a couple years I've been using this approximation of a square wave that I found in some paper somewhere, can't find the paper right now, but it is basically just multiplying a sine wave by some large value, calculated to avoid extreme aliasing, and putting it through tanh distortion. Not perfect but you can go a lot higher without noticeable aliasing than with a naive approach and as a benefit supports hard sync using a phasor like James said:

var phase = Phasor.ar(syncSig, freq / SampleRate.ir, 0, 1) * 2pi;
var k = 12000 * (SampleRate.ir/44100) / (freq * log10(freq));
var sinSig = SinOsc.ar(0, phase);
var sqSig = tanh(sinSig * k);

a little more complicated to implement variable pulse width but I can share my solution to that too if anyone's interested..

On Sat, May 29, 2021 at 9:14 PM <jamshark70@xxxxxxxxx> wrote:
On Sun, May 30, 2021 at 7:14 AM <chanof@xxxxxxxxxxx> wrote:
> Hi there,
> May ask to you how to archive an hardsynced sware wave and the best practice to antialiasing,

First, the bad news -- anti-aliasing is not well supported in
SuperCollider. AFAIK would have to write it in C++ (or FAUST) as a
binary UGen. (The most common way to handle antialiasing is
oversampling -- compute the geometric waveform, with aliasing, but at
4x or 8x the sampling rate, and then use a filter to band-limit the
signal *before* downsampling to the final rate. SC server doesn't have
oversampling between UGens. A single UGen can oversample internally,
but that needs C++ or FAUST. The other way might be related to BLEP
but we don't have a way to apply that to an arbitrary signal.)

But, a hard-synced pulse wave is not too difficult, if you take it apart.

Many types of oscillators can be viewed in terms of the relationship
between an increasing phase and the final output, e.g. a sinewave:

```
p = {
    var phase = Phasor.ar(0, 220 * SampleDur.ir, 0, 1);
    [phase, (phase * 2pi).sin]
}.plot;

p.superpose_(true).refresh;
p.plotColor_([Color.red, Color.green]).refresh;
```

(It will look the same if you use `SinOsc.ar(220)` instead of the
`sin` operator.)

OK, now a square wave.

```
p = {
    var phase = Phasor.ar(0, 220 * SampleDur.ir, 0, 1);
    [phase, LFPulse.ar(220, 0.5)]
}.plot;

p.superpose_(true).refresh;
p.plotColor_([Color.red, Color.green]).refresh;
```

In the relationship that emerges is: When the phasor is above a
threshold, push it to +1; when below, -1. The threshold determines the
pulse width.

```
(
a = {
    var freq = MouseY.kr(200, 600, 1);
    var width = MouseX.kr(0.01, 0.99, 0);
    var ph = Phasor.ar(0, freq * SampleDur.ir, 0, 1);

    // (ph > width) is 0 to 1
    // we need -1 to 1
    var pulse = (ph > width) * 2 - 1;

    // width != 0.5 will have some DC offset
    pulse = LeakDC.ar(pulse);

    (pulse * 0.1).dup
}.play;
)

a.release;  // when done
```

You can't hard sync Pulse or LFPulse, but... you can do it to Phasor.
And we know how to turn a Phasor into a (non-bandlimited ;'-/ ) pulse
wave.

```
(
a = {
    var freq = MouseX.kr(200, 1200, 1);
    var width = LFDNoise3.kr(0.2).range(0.15, 0.5);
    var syncfreq = MouseY.kr(50, 200, 1);
    var syncOsc = SinOsc.ar(syncfreq);

    // 'trigger' when syncOsc has an upward zero crossing
    var ph = Phasor.ar(syncOsc, freq * SampleDur.ir, 0, 1, 0);

    var pulse = (ph > width) * 2 - 1;

    pulse = LeakDC.ar(pulse);
    (pulse * 0.1).dup
}.play;
)

a.release;  // when done
```

(Move the mouse left/right for the classic sync sweep effect. At
moderate frequencies it's not too bad.)

hjh

_______________________________________________
sc-users mailing list

info (subscription, etc.): http://www.birmingham.ac.uk/facilities/ea-studios/research/supercollider/mailinglist.aspx
archive: https://listarc.bham.ac.uk/marchives/sc-users/
search: https://listarc.bham.ac.uk/lists/sc-users/search/