Signal Theory Basics

Fundamental concepts for understanding and processing signals.

Signal Types

Continuous-Time vs Discrete-Time

Continuous-Time Signal: $x(t)$, defined for all $t \in \mathbb{R}$

Discrete-Time Signal: $x[n]$, defined only at integer values $n \in \mathbb{Z}$

Analog vs Digital

Analog: Continuous in both time and amplitude

Digital: Discrete in both time and amplitude (quantized)


Sampling

Converting continuous-time signals to discrete-time.

Sampling Theorem (Nyquist-Shannon)

A bandlimited signal with maximum frequency $f_{max}$ can be perfectly reconstructed if sampled at:

$$ f_s \geq 2f_{max} $$

Where:

  • $f_s$ = sampling frequency
  • $f_{Nyquist} = \frac{f_s}{2}$ = Nyquist frequency (maximum representable frequency)

Sampling Process

$$ x[n] = x(nT_s) $$

Where $T_s = \frac{1}{f_s}$ is the sampling period.


Aliasing

When $f_s < 2f_{max}$, high-frequency components appear as lower frequencies (aliases).

Aliased Frequency

$$ f_{alias} = |f_{signal} - k \cdot f_s| $$

Where $k$ is chosen so that $f_{alias} < \frac{f_s}{2}$.

Prevention

  1. Anti-aliasing filter: Low-pass filter before sampling
  2. Oversample: Use $f_s \gg 2f_{max}$
 1import numpy as np
 2import matplotlib.pyplot as plt
 3
 4# Demonstrate aliasing
 5fs = 100  # Sampling frequency
 6t = np.arange(0, 1, 1/fs)
 7
 8# Signal at 30 Hz (below Nyquist)
 9f1 = 30
10x1 = np.sin(2 * np.pi * f1 * t)
11
12# Signal at 70 Hz (above Nyquist, will alias to 30 Hz)
13f2 = 70
14x2 = np.sin(2 * np.pi * f2 * t)
15
16# Both look identical when sampled!
17print(f"Nyquist frequency: {fs/2} Hz")
18print(f"Signal 1: {f1} Hz")
19print(f"Signal 2: {f2} Hz (aliases to {fs - f2} Hz)")

Basic Signal Properties

Energy Signal

Total energy is finite:

$$ E = \int_{-\infty}^{\infty} |x(t)|^2 dt < \infty $$

Discrete:

$$ E = \sum_{n=-\infty}^{\infty} |x[n]|^2 < \infty $$

Power Signal

Average power is finite:

$$ P = \lim_{T \to \infty} \frac{1}{T} \int_{-T/2}^{T/2} |x(t)|^2 dt < \infty $$

Discrete:

$$ P = \lim_{N \to \infty} \frac{1}{2N+1} \sum_{n=-N}^{N} |x[n]|^2 < \infty $$


Common Signals

Unit Impulse (Dirac Delta)

Continuous:

$$ \delta(t) = \begin{cases} \infty & t = 0 \ 0 & t \neq 0 \end{cases}, \quad \int_{-\infty}^{\infty} \delta(t) dt = 1 $$

Discrete (Kronecker Delta):

$$ \delta[n] = \begin{cases} 1 & n = 0 \ 0 & n \neq 0 \end{cases} $$

Unit Step

Continuous:

$$ u(t) = \begin{cases} 1 & t \geq 0 \ 0 & t < 0 \end{cases} $$

Discrete:

$$ u[n] = \begin{cases} 1 & n \geq 0 \ 0 & n < 0 \end{cases} $$

Sinusoid

$$ x(t) = A \cos(2\pi f t + \phi) $$

Where:

  • $A$ = amplitude
  • $f$ = frequency (Hz)
  • $\phi$ = phase (radians)

Complex Exponential

$$ x(t) = Ae^{j(2\pi f t + \phi)} = A[\cos(2\pi f t + \phi) + j\sin(2\pi f t + \phi)] $$


System Properties

Linearity

A system is linear if:

$$ T{ax_1[n] + bx_2[n]} = aT{x_1[n]} + bT{x_2[n]} $$

Time-Invariance

A system is time-invariant if:

$$ y[n] = T{x[n]} \implies y[n-k] = T{x[n-k]} $$

LTI Systems

Linear Time-Invariant systems are fundamental in signal processing:

  • Completely characterized by impulse response $h[n]$
  • Output is convolution: $y[n] = x[n] * h[n]$
  • Frequency response: $H(f) = \mathcal{F}{h[n]}$

Practical Implementation

Python

 1import numpy as np
 2
 3def is_power_of_two(n):
 4    """Check if n is a power of 2 (useful for FFT)"""
 5    return n > 0 and (n & (n - 1)) == 0
 6
 7def next_power_of_two(n):
 8    """Find next power of 2 >= n"""
 9    return 2**int(np.ceil(np.log2(n)))
10
11def normalize_signal(x):
12    """Normalize signal to [-1, 1]"""
13    return x / np.max(np.abs(x))
14
15def generate_tone(frequency, duration, sample_rate=44100):
16    """Generate a pure tone"""
17    t = np.arange(0, duration, 1/sample_rate)
18    return np.sin(2 * np.pi * frequency * t)
19
20def add_noise(signal, snr_db):
21    """Add white Gaussian noise with specified SNR"""
22    signal_power = np.mean(signal**2)
23    noise_power = signal_power / (10**(snr_db/10))
24    noise = np.sqrt(noise_power) * np.random.randn(len(signal))
25    return signal + noise

Go

 1package signals
 2
 3import "math"
 4
 5// GenerateTone creates a pure sine wave
 6func GenerateTone(freq, duration, sampleRate float64) []float64 {
 7    numSamples := int(duration * sampleRate)
 8    signal := make([]float64, numSamples)
 9    
10    for i := 0; i < numSamples; i++ {
11        t := float64(i) / sampleRate
12        signal[i] = math.Sin(2 * math.Pi * freq * t)
13    }
14    
15    return signal
16}
17
18// Normalize scales signal to [-1, 1]
19func Normalize(signal []float64) []float64 {
20    maxAbs := 0.0
21    for _, v := range signal {
22        if abs := math.Abs(v); abs > maxAbs {
23            maxAbs = abs
24        }
25    }
26    
27    normalized := make([]float64, len(signal))
28    for i, v := range signal {
29        normalized[i] = v / maxAbs
30    }
31    
32    return normalized
33}

Key Concepts Summary

ConceptContinuousDiscrete
Signal$x(t)$$x[n]$
Impulse$\delta(t)$$\delta[n]$
Convolution$\int x(\tau)h(t-\tau)d\tau$$\sum x[k]h[n-k]$
Fourier Transform$X(f) = \int x(t)e^{-j2\pi ft}dt$$X(e^{j\omega}) = \sum x[n]e^{-j\omega n}$

Further Reading

Related Snippets