Signal Generator

Mix noise, a steady tone, and a sweeping tone in software and watch them share one live spectrum.

Download
Blocks 4
Category Basics
Version 1.0.0
License MIT
Updated Sep 28, 2026

Blocks used

Signal Generator
Add
Spectrum Analyzer
Note

Overview

A radio display shows many signals piled on top of each other, and learning to read it is easier when you know exactly what went in. Here three Signal Generators make a noise floor, a steady tone at 1 MHz, and a chirp, a tone that sweeps from −2-2 MHz to 2 MHz every two seconds. Two Add blocks sum them, the way signals from different transmitters sum at one antenna, and a Spectrum Analyzer shows the result across a 5 MHz span.

First Steps covers blocks, wires, decibels, and the noise floor. This graph adds frequencies below zero, a signal that moves, and a waveform with more frequencies than the sample rate has room for. A note on the canvas suggests settings to try. Everything is generated in software, so no radio is needed.

How it works

Signal Generator (Noise)AddAddSpectrum AnalyzerSignal Generator (Chirp)Signal Generator (Cosine)

All three generators make complex I/Q samples at 5 MS/s, 8,192 at a time, so each update covers 1.64 ms of signal. Complex samples can carry negative frequencies, so the spectrum runs from −2.5-2.5 MHz to 2.5 MHz, and the left half shows signals just as the right half does.[1] A radio tuned to 100 MHz draws the same picture with 100 MHz at the center.

The tone has amplitude 1. The chirp has amplitude 10 and raises its frequency by 2 MHz every second, then jumps back to −2-2 MHz and starts again.

The analyzer divides the span into 8,192 frequency slices, each 610 Hz wide, the sample rate divided by the number of samples.[2] The graph has no Throttle, so the generators run as fast as the computer allows, and how fast the sweep crosses the screen depends on the machine rather than on its two seconds of signal time.

What to look for

The noise is a grainy carpet across the whole span. The tone is a fixed spike at 1 MHz and a straight stripe down the waterfall. The trace smooths each slice over many updates, so the fast chirp barely shows there. The waterfall stacks each new spectrum on top of the older ones, so the chirp draws a slanted line, and every jump back to −2-2 MHz starts a new one.

Setting the analyzer's Lineplot Averaging to 1 turns the smoothing off, and the chirp becomes a sliding spike about 20 dB above the tone, matching its tenfold amplitude. Setting the noise generator's Amplitude to 0.1 lowers the carpet by 20 dB and leaves both spikes where they were. Setting the tone's Frequency to −1.5-1.5 MHz moves its spike into the left half. Shortening the chirp's Duration to 0.5 seconds makes it sweep four times faster, so each slanted line covers fewer waterfall rows.

Switching the tone to Square shows aliasing. A square wave adds harmonics at odd multiples of its frequency, mirrored around zero,[2] but here the 3 MHz harmonic lies past the 2.5 MHz edge. At this sample rate it produces the same samples as a tone at −2-2 MHz, so it shows up there instead.[1] Higher harmonics fold onto the same few places, leaving spikes at ±1\pm 1 MHz, ±2\pm 2 MHz, and the center.

Raising Buffer Size to 16,384 on all three generators halves the slice width to 305 Hz and doubles the signal time per update.

For a gentle introduction to I/Q samples, spectra, and aliasing, see the sources below.

Going further

A Python block can show how fast the graph runs compared with real time. Give it one input and no outputs, connect it to the second Add block's output, and paste the code below. Once per second it prints the frequency of the tallest spike, the chirp, and how many seconds of signal the graph makes per second of wall clock.

PYTHON
import time

import numpy as np

_START = None
_SAMPLES = 0
_LAST = 0.0


def compute(ctx):
    global _START, _SAMPLES, _LAST
    x = np.asarray(ctx.inputs[0])
    rate = float(ctx.input_attrs[0].get("sampleRate", 5e6))
    now = time.monotonic()
    if _START is None:
        _START = _LAST = now
        return
    _SAMPLES += x.size
    if now - _LAST < 1.0:
        return
    _LAST = now
    peak = np.argmax(np.abs(np.fft.fftshift(np.fft.fft(x))))
    freq = (peak - x.size // 2) * rate / x.size
    speed = _SAMPLES / rate / (now - _START)
    print(f"Strongest: {freq / 1e6:+.3f} MHz, {speed:.1f}x real time")

A reading above 1.0x means the sweep on screen is faster than two seconds.

All three generators must share the same sample rate, buffer size, and data type. Frequencies must stay within ±2.5\pm 2.5 MHz, and real waveforms such as Square accept no negative ones. The Spectrum Analyzer entry feeds the same display from a real radio, and Python QPSK Generator generates its signal in Python. The block catalog describes every block used here, and the Python block reference explains inputs, attributes, and the block console.

References

References

  1. M. Lichtman, "IQ sampling," PySDR: A Guide to SDR and DSP using Python. pysdr.org/content/sampling.html ↩ ↩2

  2. M. Lichtman, "Frequency domain," PySDR: A Guide to SDR and DSP using Python. pysdr.org/content/frequency_domain.html ↩ ↩2

CyberEther

High-performance GPU-accelerated signal processing and visualization framework that runs anywhere.

Star on GitHub

© 2026 CyberEther. All rights reserved.

The final frontier!