On Fri, May 17, 2002 at 12:52:27PM +0100, mike rawes wrote:
> I'd be interested to see how you did yours (in
> particular the anti-aliasing hack - do you use
> wavetables?)
Mine is based on a pretty simple, naive approach:
Sharp edges on waveforms produce infinite harmonics
and therefore aliasing; therefore let's get rid
of the sharp edges. Amazingly, it sounds pretty good.
To do that, I have two wavetables: a full cycle of
a triangle wave, which we loop through at the frequency
we want, and a half-cycle of a sine wave at 90 degrees
so it starts at -1. The triangle wave is normalized to
the range 0 ... 1 and then scaled to the length of the half-sine,
and used as an index to get a value from the half-sine table.
So far, all this does is produce a sine wave. But if
we clip the triangle wave and rescale it so it's
always in the range 0 ... length of half-sine,
we can get many interesting effects.
For instance, if the lower limit of the triangle
(before rescaling) is 0.45 and the upper limit
is 0.55, the output of the table lookup will be
very much like a square wave except the transition
will be a smooth, steep curve instead of a sharp break.
If the lower limit is 0.9 and the upper limit is 1,
the output of the table lookup will be a pretty smooth
pulse.
So we need to control two variables, the upper and
lower bound of the clipping range. These are
controlled and modulated however you want to produce
the desired pseudo-pulse, but then they are scaled
appropriately with the frequency of the note such that
increasing the frequency forces the upper limit to be
closer to 1 and teh lower llimit to be closer to 0.
So at the nyquist frequency, the output is a pure sine wave.
Make sense? Anyway, pwm2.saol gives the idea, though
if you're not familiar with saol you might have to look
up some things here:
http://www.cs.berkeley.edu/~lazzaro/sa/book/index.html
--
Paul Winkler
home: http://www.slinkp.com
"Muppet Labs, where the future is made - today!"
// new SAOL translation attempt
global {
srate 44100;
krate 8820;
outchannels 1;
ksig gkLFO, gkamp;
}
instr globalfade (risetime) {
// p4 is rise AND decay time
exports ksig gkamp;
// WARNING: this will break if tempo goes too fast!
// Time args are in seconds, not score beats!!!
gkamp = kexpon(0.005,
risetime, 1,
dur - (risetime * 2), 1,
risetime , 0.005);
}
instr globalLFO (startrate, endrate) {
ksig lfo_freq;
exports ksig gkLFO;
imports table sine;
lfo_freq = kline(startrate, dur, endrate);
gkLFO = 0.5 + 0.5 * koscil(sine, lfo_freq);
// normalized from 0 to 1
}
instr pwm(initpitch, endpitch, moddepth) {
// another try by PW
// We simulate a square wave by overdriving a table index,
// with a halfsine wav; thus, aliasing can be reduced by
// scaling the amount of overdrive inversely with pitch.
// It's not strictly band-limited, but it sounds pretty good.
// We then simulate pulse-width modulation by
// varying an offset to the index.
imports table halfsine, triangle;
imports ksig gkLFO;
imports ksig gkamp;
ivar initfreq, endfreq, iantialias;
ksig ksquareness;
ksig kfreq;
ksig halfsinelen; // only needed at i-rate, but oh well.
ksig knarrowness, kamp, ksmoothness;
ksig highlimit, lowlimit;
ksig kcenter, kwidth;
asig index, aout, afiltered, rawindex;
initfreq = cpspch(initpitch);
endfreq = cpspch(endpitch);
// clamp to nyquist
initfreq = min(initfreq, s_rate / 2);
endfreq = min(endfreq, s_rate / 2);
kfreq = kexpon(initfreq, dur, endfreq);
iantialias = 4; //recommended range is 4 to about 10
// 7 is adequate to almost totally eliminate aliasing from square
ksmoothness = min(1, kfreq * iantialias / s_rate);
// 1 (the maximum) yields a nearly pure square wave.
// 0 yields a pure sine wave.
kcenter = (gkLFO * moddepth * (1 - ksmoothness)) * 0.5 + 0.5;
kwidth = ksmoothness;
// Suggested range for moddepth is 0 to 1.
// prevent clipping points from going out of bounds
kwidth = min(kwidth, 0.5);
kwidth = max(kwidth, 0);
kcenter = min(kcenter, 1);
kcenter = max(kcenter, 0.5);
highlimit = min(1, kcenter + kwidth);
lowlimit = max(0, kcenter - kwidth);
if (highlimit == lowlimit) {
// avoid divide-by-zero... and some work!
index = 0;
}
else {
index = oscil(triangle, kfreq);
// clip it
index = min(index, highlimit);
index = max(index, lowlimit);
// normalize - first the floor...
index = index - lowlimit ;
// ... now normalize the peak to 1
index = index / (highlimit - lowlimit);
}
halfsinelen = ftlen(halfsine) -1;
// now we've finally got something suitable for
// using as an index.
aout = tableread(halfsine, index * halfsinelen);
// Attempt to remove DC offset... do I need somethign better?
afiltered = hipass(aout, 15);
// and smooth it a bit.
//afiltered = lopass(afiltered, 8000);
// apply global gain control, and declick.
kamp = gkamp * kline(0,
.01, 1,
.02, .5,
dur -.23, .5,
.2, 0);
output(aout * kamp);
}