Band limited vs Non band limited

Try this:

(
{
	var simulatedSampleRates = [22050, 16000, 8000, 4000, 2000, 1000];
	var sinFreq = 1000;
	Latch.ar(SinOsc.ar(sinFreq), Impulse.ar(simulatedSampleRates))
}.plot
)

The farther down the window you go, the lower the simulated sample rate is. When you get to a sample rate that’s the same frequency as the signal, it just gives you one value because all of the movement of the sine wave is happening between the time that it’s being sampled. Like if you stand up when someone is facing away from you and then sit down before they turn back around.

It’ll also cause you to end up with frequencies that weren’t intended:

(
{
	var simulatedSampleRates = [22050, 4000, 2000, 2500];
	var sinFreq = 1000;
	Latch.ar(SinOsc.ar(sinFreq), Impulse.ar(simulatedSampleRates))
}.plot
)

The last plot looks like it only oscillates 5 cycles over the length of the plot instead of 10 as it should because of downsampling.

1 Like

This is a bit of a misleading point.

This result looks like it has a lower periodicity, but it is the digital representation of the 1000 Hz sine wave at 2500 Hz sampling rate (analogous to a 17640 Hz sine wave at 44.1 kHz).

The Nyquist frequency at 2500 Hz is 1250 Hz. 1000 is less than this, so there is no aliasing and no loss of this frequency information. If you up-sample the given (admittedly bizarre-looking) series of samples, you will get the 1000 Hz sine wave back, cleanly.

We can do that in SC.

(
p = {
	var simulatedSampleRates = [22050, 4000, 2000, 2500];
	var sinFreq = 1000;
	Latch.ar(SinOsc.ar(sinFreq), Impulse.ar(simulatedSampleRates))
}.plot
)

a = p.value.last;
a.size  // 480 since I'm at 48000 Hz now

// eliminate the zero-order hold
// this will give us the samples directly
a = a.separate { |a, b| a != b }.collect { |clump| clump[0] };

// a.size  // 25

// for FFT, we need a power of 2
a = a.wrapExtend(32);

// Because this does not wrap around exactly,
// we should apply a window
a = a * Signal.hanningWindow(a.size);
a.plot;

// At this point, the plot looks very messy

// Get the 32-point FFT = 16 distinct bins
// DC, bin 1, bin 2, 3, 4 ... 16 (this is Nyquist), 15, 14, 13, ... 2, 1
// = 32 bins, 16 distinct partials + DC
f = a.as(Signal).fft(Signal.newClear(a.size), Signal.fftCosTable(a.size)).asPolar;

// To up-sample, we need to manufacture more FFT bins
// The original bins are still 0 to 16 -- bandlimited!
// 17 etc. will all be zero energy
(
~insertZeros = { |array, newSize|
	var half = array.size div: 2;
	array[.. half]
	++
	Array.fill(newSize - array.size, 0)
	++
	array[half + 1 ..]
};

g = Polar(
	~insertZeros.(f.rho, 512),
	~insertZeros.(f.theta, 512)
);
)

// inverse FFT
g = g.asComplex;
h = g.real.as(Signal).ifft(g.imag.as(Signal), Signal.fftCosTable(g.real.size));

h.real.plot;  // show and tell

At this point, the magnitude is very small because of FFT normalization (the energy that had been spread over 32 samples is now spread out over 512) – but what you don’t see is a distorted sine wave (it’s clean) or a reduced frequency. (If it were true that the 2500 Hz plot reflected five cycles in the frame, then the IFFT here would show 6 or 7 cycles… but it doesn’t.)

There’s a brilliant video from xiph – the creators of the ogg format – where they take analog signal generators, oscilloscopes and frequency analyzers, and digitize the signal up to 20000 Hz @ 44.1 kHz (i.e. 2.205 samples per sine cycle!) and show that the DAC reconstruction to analog is still a pure sine wave. I think this video should be required viewing for all digital audio professionals – it efficiently disabuses the viewer of several misconceptions, about this, and dithering, and time resolution.

hjh

3 Likes