Skip to content

Nyquist-Shannon Sampling

Copy this iframe to your website:

1
<iframe src="https://dmccreary.github.io/signal-processing/sims/nyquist-shannon-sampling/main.html" width="100%" height="502px" scrolling="no"></iframe>

Run MicroSim in Fullscreen

Edit in the p5.js Editor

Description

This MicroSim samples a 5 Hz sine wave, \(x(t) = \sin(2\pi \cdot 5t)\), at the sampling rate \(f_s\) set by the slider (1 to 50 Hz in steps of 0.1 Hz) and shows one second of the result.

  • Upper plot: the original signal in black and its samples \(x[n] = x(n/f_s)\) as red dots.
  • Lower plot: two continuous signals rebuilt from the samples alone. The blue curve is the ideal (sinc) reconstruction that the sampling theorem promises. The gray curve joins neighboring samples with straight lines.

The read-out line gives the signal frequency \(f\), the Nyquist rate \(2f = 10\) Hz, the Nyquist frequency \(f_s/2\) and the number of samples per period \(f_s/f\). The colored status line is the Nyquist indicator: green when \(f_s > 2f\), gold when \(f_s = 2f\) exactly, and red when \(f_s < 2f\), in which case it reports the alias frequency.

This is the introductory sampling sim for the sections on the sampling theorem and aliasing in Chapter 5: Sampling and Quantization. The Sampling Theorem Explorer adds spectra and more controls.

Mathematical Concepts

The blue curve is the Whittaker-Shannon interpolation of the samples,

\[x_r(t) = \sum_{n} x[n]\,\operatorname{sinc}(f_s t - n), \qquad \operatorname{sinc}(u) = \frac{\sin(\pi u)}{\pi u}.\]

The theorem needs every sample from \(n = -\infty\) to \(\infty\). The sim uses the samples in the window plus 2000 more on each side, which keeps the blue curve within 0.01 of the exact result for every slider setting.

When \(f_s > 2f\) the sum returns the original 5 Hz wave. When \(f_s < 2f\) the samples are identical to the samples of a slower sine at the alias frequency

\[f_a = |f - k f_s|,\]

where the integer \(k\) makes \(f_a \le f_s/2\), and the sum returns that alias instead.

Lesson Plan

Learning Objectives

After using this MicroSim, students will be able to:

  1. State the Nyquist criterion \(f_s > 2f\) and calculate the Nyquist rate of a sinusoid of known frequency
  2. Calculate the samples per period \(f_s/f\) and the Nyquist frequency \(f_s/2\) for a given sampling rate
  3. Predict the alias frequency \(f_a = |f - k f_s|\) when \(f_s < 2f\) and verify it by counting the cycles of the reconstruction
  4. Distinguish ideal (sinc) reconstruction from straight-line interpolation and explain why a jagged straight-line plot does not mean that information was lost
  5. Explain why sampling at exactly \(f_s = 2f\) can fail to capture a sine wave

Target Audience

Undergraduate students in a first signals and systems or digital signal processing course. Plan 20 minutes for the activities below.

Prerequisites

  • Frequency and period of a sine wave (\(f = 1/T\))
  • Sampling as taking the values \(x[n] = x(nT_s)\) every \(T_s = 1/f_s\) seconds
  • Reading a plot of amplitude against time

Activities

Activity 1: Above the Nyquist rate (5 min). Start at the default \(f_s\) = 22.0 Hz. The read-out shows 4.40 samples per period and \(f_s/2\) = 11.00 Hz, and the status line is green. The blue curve passes through every red dot and matches the black original, while the gray straight lines cut the corners off each peak. Lower \(f_s\) to 12.0 Hz (2.40 samples per period): the gray zigzag no longer looks like a sine, yet the blue curve is still the 5 Hz wave.

Activity 2: Find the edge (5 min). Set \(f_s\) = 10.5 Hz (2.10 samples per period): the status is still green and the blue curve still completes 5 cycles in one second. Set \(f_s\) = 10.0 Hz: the status turns gold, the 11 samples fall at \(t\) = 0, 0.1, ..., 1.0 s on zero crossings, and both reconstructions are flat lines. Set \(f_s\) = 9.5 Hz: the status turns red and reports a 4.5 Hz alias. The blue curve completes 4.5 cycles and starts downward instead of upward.

Activity 3: Predict the alias (5-10 min). Before moving the slider, compute \(f_a = |5 - k f_s|\) for \(f_s\) = 8.0, 6.0 and 4.5 Hz. Then check: at 8.0 Hz the status reports 3.0 Hz and the blue curve completes 3 cycles; at 6.0 Hz it reports 1.0 Hz and the blue curve completes one cycle; at 4.5 Hz it reports 0.5 Hz and the blue curve rises to +1 at \(t\) = 0.5 s and falls back to 0 at \(t\) = 1 s, half a cycle.

Activity 4: Samples that vanish (5 min). Set \(f_s\) = 5.0 Hz (1.00 sample per period). Every sample lands at the same point of the cycle, where \(\sin(2\pi n) = 0\), so the status reads "Every sample is 0" and the reconstruction is flat: the 5 Hz wave has aliased to 0 Hz. Try 2.5 Hz and 2.0 Hz and explain why the samples are again all zero.

Assessment

  1. A 5 Hz sine wave is sampled at 8 Hz. What frequency does the reconstruction show? Answer: 3 Hz, since \(|5 - 8| = 3\) Hz is below \(f_s/2 = 4\) Hz.
  2. What is the lowest sampling rate that captures a 5 Hz sine wave, and why does exactly 10 Hz fail in this sim? Answer: any rate above 10 Hz. At exactly 10 Hz the samples fall at \(t = n/10\), where \(\sin(\pi n) = 0\), so every sample is zero.
  3. At \(f_s\) = 12 Hz the gray curve looks nothing like a sine wave. Has information been lost? Answer: no. 12 Hz is above the 10 Hz Nyquist rate, and the blue sinc reconstruction recovers the 5 Hz wave; the distortion comes from straight-line interpolation, not from sampling.
  4. CD audio is sampled at 44.1 kHz. What is the highest frequency it can represent, and where does a 30 kHz tone appear if it reaches the converter unfiltered? Answer: 22.05 kHz; the 30 kHz tone aliases to \(|30 - 44.1| = 14.1\) kHz.
  5. How many samples per period does the default setting take, and how many does the Nyquist rate take? Answer: 4.4 samples per period at 22 Hz; exactly 2 at the 10 Hz Nyquist rate.

Common Misconceptions

"A sine wave needs many samples per period, say ten, to be captured." At \(f_s\) = 12 Hz there are only 2.4 samples per period and the straight-line plot looks badly distorted, but the blue sinc reconstruction is the original 5 Hz wave. The number of samples needed is set by the theorem, not by how the dots look when joined with straight lines.

"Sampling at exactly twice the frequency is enough." At \(f_s\) = 10.0 Hz every sample of this sine is zero and the wave disappears. The criterion is a strict inequality, \(f_s > 2f\), and practical systems sample well above it.

"Aliasing makes a signal look noisy." At \(f_s\) = 8.0 Hz the reconstruction is a clean, smooth sine wave. It is simply the wrong one: 3 Hz instead of 5 Hz. Nothing in the samples reveals that it is an alias.

References

  1. Nyquist-Shannon sampling theorem - Wikipedia - statement of the theorem, the condition \(f_s > 2B\), and sinc reconstruction
  2. Aliasing - Wikipedia - how frequencies above \(f_s/2\) fold to lower alias frequencies
  3. Nyquist rate - Wikipedia - the minimum sampling rate \(2B\) for a signal of bandwidth \(B\)
  4. Nyquist frequency - Wikipedia - the highest frequency \(f_s/2\) that a given sampling rate can represent
  5. Whittaker-Shannon interpolation formula - Wikipedia - the sinc sum used to draw the blue curve
  6. The Scientist and Engineer's Guide to Digital Signal Processing - Steven W. Smith - free online textbook; Chapter 3 (ADC and DAC) explains the sampling theorem and aliasing

Development Notes

Prompt history

Version 2.0.0 (2026-10-14). Ported from p5.js 1.10.0 to p5.js 2.3.2 and made responsive (one createCanvas call, updateCanvasSize(), windowResized()); the legacy entry file nyquist-shannon-sampling.html now redirects to main.html. Samples are now taken at the exact times \(t = n/f_s\). The earlier sketch stepped through the canvas in whole pixels (floor(width / samplingRate)), so the true number of samples per period differed from the label (4.5 instead of the 4.4 shown at the default setting on a 450 px canvas) and changed with the canvas width. The slider keeps its range, step and default (1 to 50, 0.1, 22); its value is now labeled as the sampling rate in Hz for the 5 Hz signal shown over one second, with samples per period (\(f_s/5\), the old label) in the read-out. The lower plot keeps the original straight-line reconstruction (now gray) and adds the ideal sinc reconstruction (blue), a legend, a time axis and the Nyquist status line with the alias frequency.

Original prompt (sent to ChatGPT o1-preview)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Please help me create a p5.js simulation that helps
students understand Sampling and Aliasing. The Nyquist-Shannon
sampling theorem and the concept of aliasing involve understanding
how continuous signals are represented in discrete form. Grasping
how undersampling leads to frequency overlap (aliasing) can be non-intuitive.

I would like an Interactive p5.js program that can demonstrate how
varying the sampling rate affects the reconstructed signal.
By adjusting parameters in real-time, students can visualize
aliasing effects and the importance of adhering to the Nyquist rate.

Please create a p5.js simulation that allows students to use a
slider to adjust the sampling rate for a waveform using a slider.
Display two waveforms. One is the input with the samples drawn
as radius 7 red circles. The second waveform is one done from
the reconstruction of the sampling points.
Place the original on the top and the reconstruction under
the original.

Use the following template:

Template supplied with the prompt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// region drawing parameters
// the width of the entire canvas
let canvasWidth = 400;
// The top drawing region above the interactive controls
let drawHeight = 400;
// control region height
let controlHeight = 50;
// The total hight of both the drawing region height + the control region height
let canvasHeight = 450;
// margin around the active plotting region
let margin = 25;
// larger text so students in the back of the room can read the labels
let defaultTextSize = 16;

function setup {
    const canvas = createCanvas(canvasWidth, canvasHeight);
    // canvas.parent('canvas-container');
    var mainElement = document.querySelector('main');
    canvas.parent(mainElement);
    textSize(defaultTextSize);

    // create a new slider at th bottom of the canvas
    mySlider = createSlider(0, 100, 50, 1);
    mySlider.position(sliderLeftMargin, drawHeight + 12);
    mySlider.size(canvasWidth - 2*margin);

}

function draw() {
    // make the background drawing region a very light blue
    fill('aliceblue');
    rect(0, 0, canvasWidth, drawHeight);
    // make the background of the controls area white
    fill('white')
    rect(0, drawHeight, canvasWidth, controlHeight);

    // get the updated slider value
    val = mySlider.value();

    // put your custom code here


    // draw label and value
    text("MySlider: " +  val, 10, drawHeight + 25)
}

ChatGPT response (o1-preview, thought for 6 seconds). "Below is a p5.js simulation that demonstrates sampling and aliasing. You can adjust the sampling rate using a slider to see how it affects the reconstructed signal."

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Nyquist-Shannon Sampling Rate MicroSim
// region drawing parameters
// the width of the entire canvas
let canvasWidth = 450;
// The top drawing region above the interactive controls
let drawHeight = 350;
// control region height
let controlHeight = 50;
// The total height of both the drawing region height + the control region height
let canvasHeight = drawHeight + controlHeight;
// margin around the active plotting region
let margin = 30;
// larger text so students in the back of the room can read the labels
let defaultTextSize = 16;

let samplingRateSlider;
let samplingRate = 22; // Initial sampling rate
let signalFrequency = 5; // Frequency of the original signal
let time = [];
let signal = [];
let sampledSignal = [];

function setup() {
  createCanvas(canvasWidth, canvasHeight);
  const canvas = createCanvas(canvasWidth, canvasHeight);
  var mainElement = document.querySelector('main');
  canvas.parent(mainElement);
  textSize(defaultTextSize);

  // slider to allow the user to select the sampling rate
  samplingRateSlider = createSlider(1, 50, samplingRate, .1);
  samplingRateSlider.position(20, drawHeight + 30);
  samplingRateSlider.size(canvasWidth - margin);

  // Generate time values
  for (let t = 0; t <= width; t++) {
    time.push(t);
  }

  // Generate original signal values
  for (let i = 0; i < time.length; i++) {
    let t = time[i];
    signal[i] = sin(TWO_PI * signalFrequency * (t / width));
  }
}

function draw() {
  // Fill the drawing region background with 'aliceblue'
  fill('aliceblue');
  stroke('black');
  rect(0, 0, canvasWidth, drawHeight);

  // Fill the control region background with 'white'
  fill('white');
  rect(0, drawHeight, canvasWidth, controlHeight);

  samplingRate = samplingRateSlider.value();

  // Draw title
  textSize(24);
  fill('black');
  strokeWeight(0);
  textAlign(CENTER);
  text('Nyquist-Shannon Sampling Rate', canvasWidth / 2, 30);

  // Draw labels
  textSize(16);
  fill('black');
  strokeWeight(0);
  textAlign(CENTER);
  text('Original Signal with Sampling Points', canvasWidth / 2, 60);
  text('Reconstructed Signal from Samples', canvasWidth / 2, drawHeight / 2 + 45);
  textAlign(LEFT);
  text('Sampling Rate: ' + round(samplingRate/5,2) + ' samples per period', 20, drawHeight + 20);

  // Draw original signal
  stroke(0);
  noFill();
  strokeWeight(1.5);
  beginShape();
    for (let i = 0; i < signal.length; i++) {
      let y = map(signal[i], -1, 1, 100, 0);
      vertex(time[i], y + 80);
    }
  endShape();

  // Sample the signal
  sampledSignal = [];
  let sampleInterval = floor(width / samplingRate);
  for (let i = 0; i < time.length; i += sampleInterval) {
    let y = map(signal[i], -1, 1, 100, 0);
    sampledSignal.push({ x: time[i], y: y + 30 });
    // Draw sampling points
    fill(255, 0, 0);
    noStroke();
    circle(time[i], y + 80, 7);
  }

  // Reconstruct the signal from samples
  stroke('blue');
  strokeWeight(2);
  noFill();
  beginShape();
  for (let i = 0; i < sampledSignal.length; i++) {
    vertex(sampledSignal[i].x, sampledSignal[i].y + height / 2);
  }
  endShape();

  // Draw reconstructed signal
  stroke(0);
  noFill();
  beginShape();
  for (let i = 0; i < sampledSignal.length - 1; i++) {
    let x1 = sampledSignal[i].x;
    let y1 = sampledSignal[i].y + height / 2;
    let x2 = sampledSignal[i + 1].x;
    let y2 = sampledSignal[i + 1].y + height / 2;
    line(x1, y1, x2, y2);
  }
  endShape();
}

How to use the original simulation (from the response)

  1. Adjust the Sampling Rate: Use the slider at the bottom of the canvas to change the sampling rate. The sampling rate is displayed above the slider.
  2. Observe the Top Graph: The top waveform represents the original signal. Red circles indicate the sampling points based on the current sampling rate.
  3. Observe the Bottom Graph: The bottom waveform is reconstructed from the sampled points. As you adjust the sampling rate, notice how the reconstructed signal changes.

Explanation of the original code (from the response)

  • samplingRateSlider: the slider control for adjusting the sampling rate.
  • samplingRate: the current sampling rate value from the slider.
  • signalFrequency: the frequency of the original signal.
  • time, signal, sampledSignal: arrays that store the time values, the original signal values and the sampled points.
  • setup() initializes the canvas and the slider and computes the original signal using a sine function.
  • draw() clears the background, reads the slider, draws the labels and the original waveform, draws red circles at the sampling points, and draws the reconstruction from the sampled points in the bottom half of the canvas.

Key concepts named in the response

  • Sampling rate: the number of samples taken per period of the signal. A higher sampling rate captures the signal more accurately.
  • Aliasing: when the sampling rate is too low (below the Nyquist rate), the reconstructed signal may differ significantly from the original.

The response ended by suggesting that the code be pasted into the p5.js online editor and that students experiment with different signal frequencies.