«Band-limited hard sync oscillator» by nathanielvirgo
on 09 Oct'15 10:19 inI've always wanted a band limited version of the SyncSaw UGen. After taking a look at [1], I realised it's actually quite easy to implement this in SuperCollider. The paper uses a series of band-limited impulses and then integrates them, but since the integral of an impulse is a sawtooth wave, we can just use Saw.ar. All we need to do is sum up a variable number of them with the appropriate phases and amplitudes. Since Saw.ar doesn't have a phase control, I implemented the phase offsets using delays. (Using BufDelay means that they all share the same buffer, so keeps memory use down.)
As in SyncSaw.ar, syncFreq is the frequency of the master oscillator (i.e. the pitch) and sawFreq the frequency of the slave. minFreq is the minimum value of syncFreq (used to set the length of the delay line) and maxRatio is the maximum value of sawFreq/syncFreq, used to set the number of waves that are added together. (If you go over this you will get an incorrect waveform.)
You can try replacing the Saw.ar with a Pulse.ar in the definition of ~syncSaw. The resulting waveform does not resemble a hard-synced pulse oscillator, but it sounds good.
[1] http://www.cs.cmu.edu/~eli/papers/icmc01-hardsync.pdf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
( ~syncSaw = { arg syncFreq=440, sawFreq=440, maxRatio=20, minFreq = 10; var maxDelay = 1/minFreq; var n = floor(maxRatio); var b = LocalBuf(Server.default.sampleRate*maxDelay ,1).clear; var baseWaveform = Saw.ar(syncFreq); var sig = 0; n.do { arg i; var delay = i/sawFreq; var amplitude = (sawFreq/syncFreq-i) min: 1 max: 0; sig = sig + (BufDelayL.ar(b,baseWaveform,delay)*amplitude); }; sig; }; ) ( // test it play {~syncSaw.(100,MouseX.kr.range(100,2000).lag)!2} ) ( // compare with aliased version play {SyncSaw.ar(100,MouseX.kr.range(100,2000).lag)!2} ) ( // audio rate modulation of sawFreq results in a waveform that's not strictly // correct, but sounds good. play {~syncSaw.(MouseY.kr(50,200),MouseX.kr.range(100,2000).lag+SinOsc.ar(20,mul:150))!2} // (audio rate modulation of syncFreq doesn't really work.) ) ( // aliased version of the above play {SyncSaw.ar(MouseY.kr(50,200),MouseX.kr.range(100,2000).lag+SinOsc.ar(20,mul:150))!2} )