t_s_unit tiled over the record: P samples long,
the first “on phases” of them 1 and the rest 0.
A digital computer cannot hold a continuous-time signal, so the top left panel is an approximation: a record of N points, sampled so fast that we can pretend it is continuous. Call that rate fc. It carries a tone at f1 and, unless you turn them off, two sidebands at f1 ± Δf, plus a little Gaussian noise.
Sampling is a multiplication in the time domain by a pulse train: P samples long, the first few of them 1 and the rest 0. Multiplication in time is convolution in frequency, and the spectrum of a pulse train is a comb of lines at every multiple of fs = fc/P with a sinc envelope set by the duty cycle. So the sampled spectrum, bottom right, is the original spectrum copied to every multiple of fs.
np.hanning is in every one
of these scripts.
ex/dt.py deliberately parks its tone halfway between two
bins (f1 = 233.5/N), so its record is never coherent and the window
always has a job to do.
ex/sub.py's
single-tone case, and drop the record length to 210 to watch
the noise floor rise: fewer FFT bins means the same noise power is shared
between fewer of them.
#- Create the "continuous time" signal
f1 = 233/N
fd = 1/N*119
x_s = np.sin(2*np.pi*f1*t) + 1/1024*np.random.randn(N) \
+ 0.5*np.sin(2*np.pi*(f1-fd)*t) + 0.5*np.sin(2*np.pi*(f1+fd)*t)
#- Create the sampling vector, and the sampled signal
t_s_unit = [1,1,0,0,0,0,0,0]
t_s = np.tile(t_s_unit,int(N/len(t_s_unit)))
x_sn = x_s*t_s
#- Hanning window to avoid FFT bin energy spread
w = np.hanning(N+1)
X_s = np.fft.fftshift(np.fft.fft(np.multiply(w[0:N],x_s)))
X_sn = np.fft.fftshift(np.fft.fft(np.multiply(w[0:N],x_sn)))
Source: ex/dt.py, ex/sub.py. The page uses the same formulas; the noise is seeded so the floor stays put while you drag a slider.