hey, ive recreated the band-limited wavetable approach from the go book.
Its using windowed sinc interpolation and mipmapping.
The result is pretty good anti-aliased even for high index FM.
// prepare buffers
(
var getSinc = { |numRipples|
var x = Array.interpolation(4096, -1.0, 1.0);
sincPi(x * 0.5pi * numRipples);
};
var sinc = getSinc.(8);
var kaiser = Signal.kaiserWindow(4096);
var windowedSinc = sinc * kaiser;
~sincBuf = Buffer.loadCollection(s, windowedSinc);
)
(
~sndBuf = Buffer.alloc(s, 2048, 1, { |buf|
buf.sine1Msg((1..512).reciprocal, asWavetable: false)
});
)
// run example!
(
var rampToSlope = { |phase|
var history = Delay1.ar(phase);
var delta = (phase - history);
delta.wrap(-0.5, 0.5);
};
var sincInterpolated = { |phase, sndBuf, sincBuf, sampleSpacing, numSamples = 8|
var sampleIndex, fracPart, intPart;
var samples, windows;
var halfSamples = numSamples.div(2);
// Calculate the base sample position
sampleIndex = phase * BufFrames.kr(sndBuf) / sampleSpacing;
fracPart = sampleIndex.wrap(0, 1); // fractional part for interpolation
intPart = sampleIndex - fracPart; // integer part for base position
// Get sample values at integer offsets
samples = (halfSamples.neg..(halfSamples - 1)).collect{ |offset|
var readPos = ((intPart + offset) * sampleSpacing).wrap(0, BufFrames.kr(sndBuf));
BufRd.ar(1, sndBuf, readPos, interpolation: 1);
};
// Get window values from sinc function
windows = (1..numSamples).collect{ |i|
var sincPos = (i / numSamples) - (fracPart / numSamples) * BufFrames.kr(sincBuf);
BufRd.ar(1, sincBuf, sincPos, interpolation: 4);
};
(samples * windows).sum;
};
var mipmapInterpolate = { |phase, sndBuf, sincBuf|
var slope, samplesPerFrame, octave, layer;
var spacing1, spacing2, sig1, sig2;
// Calculate mipmap parameters
slope = rampToSlope.(phase);
samplesPerFrame = slope.abs * BufFrames.kr(sndBuf);
octave = max(0, log2(samplesPerFrame));
layer = octave.ceil;
// Calculate spacings for adjacent mipmap levels
spacing1 = 2 ** layer;
spacing2 = 2 ** (layer + 1);
// Get and crossfade interpolated signals
sig1 = sincInterpolated.(phase, sndBuf, sincBuf, spacing1);
sig2 = sincInterpolated.(phase, sndBuf, sincBuf, spacing2);
LinXFade2.ar(sig1, sig2, octave.wrap(0, 1) * 2 - 1);
};
{
var rate, phase, sig;
rate = SinOsc.ar(0.1, 1.5pi).linlin(-1, 1, 100, 8000);
phase = Phasor.ar(DC.ar(0), rate * SampleDur.ir);
sig = mipmapInterpolate.(phase, ~sndBuf, ~sincBuf);
sig = LeakDC.ar(sig);
sig!2 * 0.1;
}.play;
)
// check the freqscope!
s.freqscope;