os_fact = 100; # oversampling factor
imp_bw = 10; # impulse bandwidth relative to antialiasing filter
# offsets:
# imp_bw=5: (~8-bit null)
# os_fact=100: +0.661
# imp_bw=10: (~10-bit null)
# os_fact=100: +0.539
# imp_bw=82: (~16-bit null)
# os_fact=10: +0.4739
# os_fact=100: +0.498
offset = -os_fact/pi+0.539;
x = [0:os_fact*imp_bw*100-1];
# finite-bandwidth impulse (first-order low pass response)
y_imp = exp(-x/os_fact*pi)/os_fact*pi;
# windowed sinc antialiasing filter
y_aa = sinc((x-length(x)/2-offset)/os_fact/imp_bw) .* blackman(length(x))';
# convolve impulse with antialiasing filter
y_flt = fftconv(y_imp, y_aa);
y_flt = y_flt / max(y_flt); # normalize
# decimate to get output samples
y_out = y_flt(1:os_fact*imp_bw:length(y_flt));
# residual (output minus ideal unit impulse)
residual = horzcat(y_out(1:50), y_out(52:101));
max_residual_dBFS = 20*log10(max(abs(residual)))
fig = figure;
# octave doesn't seem to like plotting a huge number of points...
plot(x(1:imp_bw:length(x))/os_fact/imp_bw, y_aa(1:imp_bw:length(y_aa)));
hold on;
plot([0:100], y_out(1:101));
hold off;
legend('Antialiasing filter', 'Output samples');
title(sprintf('Impulse Response — %dx Analog Bandwidth', imp_bw));
xlabel('Output Sample');
grid on;
grid minor;
print(fig, 'impulse.png', '-S800,600');
out_mag = 20*log10(abs(fft(y_out))(1:length(y_out)/2+1));
semilogx([1:length(out_mag)-1]/(length(out_mag)-1), out_mag(2:length(out_mag)));
title(sprintf('Digital Output Magnitude — %dx Analog Bandwidth', imp_bw));
xlabel('Normalized Frequency');
ylabel('Magnitude (dBFS)');
grid on;
print(fig, 'impulse_mag.png', '-S800,600');