Skip to content

Lab 11: Sine Waves — Amplitude, Frequency, Phase

Time: ~40 minutes | Prerequisites: Lab 10 | Hardware: Pico 2 (no microphone needed)

Now we build signals instead of catching them

Echo waving welcome Module 2 was about catching sound. Module 3 is about understanding it — and to do that we need signals where we already know the answer. You can only trust a tool you've tested on something known. Time to transform!

What You'll Build

Sine waves from scratch, in code, with the three knobs that define any wave — and a console plot so you can see what each knob does.

Learning Objectives

  • Generate a sine wave from a frequency, amplitude and phase
  • Convert a sample index into a moment in time
  • Explain why angles are measured in radians
  • Relate frequency, period, and samples-per-cycle
  • Predict how many cycles fit in a window of N samples

Concepts Introduced

ID Concept
300 Radians
301 Angular Frequency
302 Period Of A Wave
303 Phase Offset
304 Sine Synthesis
305 Sample Index To Time
306 Waveform Plotting
307 Peak Amplitude
308 DC Component
309 Signal Synthesis

Background

Three numbers describe any sine wave

1
value = amplitude * sin(2*pi*frequency*t + phase)
Knob Controls Change it and…
amplitude how tall louder or quieter
frequency how fast higher or lower pitch
phase where it starts sounds identical

That last one is worth dwelling on. Phase changes where in its cycle the wave begins. Your ear can't hear it at all for a single tone — but it will nearly break our frequency detector in Lab 13, so keep it in mind.

From sample number to time

Your samples are just a list. To use the formula you need when each one happened:

1
t = i / SAMPLE_RATE

Sample 128 at 12,800 Hz happened at 0.01 seconds. That single line connects "item in a list" to "moment in time."

Why radians

sin() repeats every . So 2*pi*frequency*t advances by exactly one full turn per cycle of the wave. Degrees would work, but you'd be writing 360 everywhere instead of 2π.

The magic frequency: rate ÷ N

Echo thinking With 64 samples at 12,800 Hz, a 200 Hz wave fits exactly one whole cycle in the window. 400 Hz fits two. Any multiple of 200 fits a whole number of cycles — and those are exactly the frequencies our detector will handle perfectly in Lab 13. Frequencies between them cause trouble, which becomes the entire subject of Lab 22.

Procedure

Step 1 — Generate and look

Open 11-sine-waves.py and run it:

 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
# Lab 11: Sine Waves -- Amplitude, Frequency, Phase
#
# No microphone this time. We MAKE the signal, so we know exactly what is in
# it. That matters more than it sounds: for the next five labs we are building
# tools, and you can only trust a tool you have tested on a known answer.
#
# A sine wave needs exactly three numbers:
#
#     value = amplitude * sin(2*pi*frequency*t + phase)
#              ^^^^^^^^^        ^^^^^^^^^        ^^^^^
#              how tall         how fast         where it starts

import config
import math

SAMPLE_RATE = config.SAMPLE_RATE      # 12800 Hz
N = 64                                # small enough to print


def make_sine(freq, amplitude=1.0, phase=0.0, n=N, rate=SAMPLE_RATE):
    """Generate n samples of a sine wave."""
    out = []
    for i in range(n):
        t = i / rate                  # sample index -> seconds
        out.append(amplitude * math.sin(2 * math.pi * freq * t + phase))
    return out


def plot(values, label, width=56):
    """Draw a waveform sideways in the console."""
    print()
    print("--- %s ---" % label)
    mid = width // 2
    for v in values:
        pos = int(mid + v * (mid - 1))
        pos = max(0, min(width - 1, pos))
        line = [" "] * width
        line[mid] = "|"
        line[pos] = "*"
        print("".join(line))


print("Sample rate: %d Hz" % SAMPLE_RATE)
print("Samples    : %d  (%.2f ms of signal)" % (N, N / SAMPLE_RATE * 1000))
print()

# --- frequency: how many cycles fit in the window -------------------------
# One full cycle across N samples means freq = rate / N.
one_cycle = SAMPLE_RATE / N
print("One cycle across %d samples = %.1f Hz" % (N, one_cycle))
print("Two cycles                  = %.1f Hz" % (2 * one_cycle))

plot(make_sine(one_cycle), "%.0f Hz -- exactly ONE cycle" % one_cycle)
plot(make_sine(2 * one_cycle), "%.0f Hz -- TWO cycles" % (2 * one_cycle))

# --- amplitude: how tall ---------------------------------------------------
plot(make_sine(one_cycle, amplitude=0.3), "same frequency, amplitude 0.3")

# --- phase: where it starts ------------------------------------------------
# A quarter turn (pi/2) converts a sine into a cosine. Same wave, shifted.
plot(make_sine(one_cycle, phase=math.pi / 2),
     "same frequency, phase shifted by pi/2 (a cosine)")

# --- the numbers behind the picture ---------------------------------------
print()
print("=== The first 8 samples of a %.0f Hz wave ===" % one_cycle)
s = make_sine(one_cycle)
for i in range(8):
    t = i / SAMPLE_RATE
    angle = 2 * math.pi * one_cycle * t
    print("  i=%d  t=%.6f s  angle=%.3f rad  sin=%+.4f" % (i, t, angle, s[i]))

print()
print("=== Why radians? ===")
print("A full circle is 2*pi = %.4f radians." % (2 * math.pi))
print("sin() repeats every 2*pi, which is exactly one cycle of the wave.")
print("Degrees would work too -- radians just make the formula tidier.")

print()
print("Period of a %.0f Hz wave = 1/%.0f = %.4f ms" %
      (one_cycle, one_cycle, 1000 / one_cycle))
print("At %d Hz sampling that is %.0f samples per cycle." %
      (SAMPLE_RATE, SAMPLE_RATE / one_cycle))

You'll see four plots: one cycle, two cycles, a quieter wave, and a phase-shifted one.

Step 2 — Compare the plots

  • One vs two cycles — same window, twice the wiggles. That's frequency.
  • Amplitude 0.3 — same shape, squashed toward the middle.
  • Phase π/2 — starts at the peak instead of the middle. Same wave, different starting point.

Step 3 — Check the numbers

1
2
i=0  t=0.000000 s  angle=0.000 rad  sin=+0.0000
i=1  t=0.000078 s  angle=0.098 rad  sin=+0.0980

Each sample is 1/12,800 s = 78 µs later, and the angle advances a little each time. After 64 samples the angle reaches 2π and the wave has come full circle.

Step 4 — Predict, then measure

Prediction: change N from 64 to 128 without changing the frequency. How many cycles appear in the plot?

Try it. Then work out why — and what frequency would restore exactly one cycle.

Expected Output

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
One cycle across 64 samples = 200.0 Hz
Two cycles                  = 400.0 Hz

--- 200 Hz -- exactly ONE cycle ---
                            *
                            | *
                            |    *
...

Period of a 200 Hz wave = 1/200 = 5.0000 ms
At 12800 Hz sampling that is 64 samples per cycle.

Troubleshooting

Symptom Likely cause Fix
Plot looks like noise Frequency above Nyquist Keep it under 6,400 Hz — you're aliasing (Lab 9)
Wave doesn't fit the window Frequency isn't a multiple of rate/N That's fine and normal; Lab 22 is all about it
Flat line Amplitude is zero, or frequency is Check your arguments
Plot fills the whole width Amplitude greater than 1.0 The plotter scales to ±1

Challenges

  1. Make a cosine two ways. Once with math.cos, once with math.sin and a phase shift. Confirm they match.
  2. Add a DC offset. Add 0.5 to every sample. Where does the wave sit now? (You met this as the microphone's bias in Lab 7.)
  3. Sub-Nyquist check. Generate a 6,000 Hz wave with 64 samples. Does it still look like a sine? How many samples per cycle do you get, and does that match Lab 9's rule?

Check Your Understanding

  1. What are the three parameters of a sine wave, and which one can't you hear?
  2. Convert sample index 100 to a time, at 12,800 Hz.
  3. Why is a full cycle 2π rather than 360?
  4. With 256 samples at 12,800 Hz, what frequency fits exactly one cycle?
  5. What's the period of a 400 Hz wave, in milliseconds?

You can build any wave now

Echo celebrating One sine is a pure tone — a bit boring. Next lab we start stacking them, which is where real sound actually comes from.


Next: Lab 12: Adding Waves | Previous: Lab 10