First Steps

A guided first flowgraph that teaches blocks, wires, and live settings without any radio hardware.

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

Blocks used

Signal Generator
Add
Throttle
Spectrum Analyzer
Constellation
Note

Overview

Every CyberEther flowgraph is built from the same few pieces, and the quickest way to learn them is to wire a small graph by hand. This flowgraph is a lesson. A note on the canvas starts you with one running Signal Generator and walks you through turning it into a spectrum analyzer, mixing in noise, and sending one signal to two displays. It also covers moving around the canvas, reading block documentation, and saving.

Blocks pass each other tensors, arrays of numbers with a fixed shape and type, and every wire here carries 8,192 complex samples per update. Every setting is live, so a change shows up on the displays right away. A finished copy of the graph sits lower on the canvas as an answer key. Everything is generated in software, so no radio is needed.

How it works

Signal Generator (Tone)AddThrottleSpectrum AnalyzerSignal Generator (Noise)Constellation

The first Signal Generator makes a 1 kHz tone at one million samples per second, 8,192 samples at a time. Its samples are complex, each one a pair of numbers called I and Q, the form radios work in.[1] For a tone, each pair is a point that circles the origin, once every 1,000 samples at this frequency. The second generator makes random noise with its Amplitude set to 0.1. The Add block sums the two sample by sample, so both generators must use the same buffer size and data type.

The Spectrum Analyzer shows how strong the signal is at each frequency, with a waterfall below it that stacks those spectra over time. The Constellation draws every sample as a dot on the I/Q plane. In the answer key, a Throttle block before the analyzer slows the updates to an easy pace to watch.

What to look for

The tone shows up as a single spike at the center, since 1 kHz is a tiny step above zero on an axis that runs from −0.5-0.5 to 0.5 MHz, and as a steady vertical stripe in the waterfall. The scale is in decibels, so halving the amplitude lowers the spike by 6 dB. Even at full amplitude the spike peaks about 8 dB below the top of the scale, because the analyzer softens the edges of each batch before measuring it.

With the noise mixed in, the floor rises into a grainy carpet. How far the spike stands above the carpet shows how well the tone stands out from the noise. Raising the noise amplitude to 0.5 lifts the carpet about 14 dB closer to the spike.

On the Constellation, the tone alone draws a clean ring. Noise blurs it into a fuzzy band, and at a noise amplitude of 0.5 the ring becomes a cloud. Setting the tone to 0.1 MHz moves each sample a tenth of the way around, so the ring collapses into ten dots, each blurred by the noise.

Switching the tone to Square adds spikes at three and five times its frequency, the odd harmonics, on both sides of center. The generator makes its square wave real, with Q always zero, and a real signal's spectrum is a mirror image around zero.[2] At 1 kHz the harmonics sit close to center, so zoom in on the analyzer to tell them apart.

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

Going further

A Python block can turn what the analyzer shows into a number. Give it one input and no outputs, connect it to the first Signal Generator's output, and paste the code below. Once per second it prints the average power of the samples in decibels, 0.0 dB for the tone at amplitude 1. Halve the amplitude and the reading drops to −6.0-6.0 dB, as the spike did. Connect it to the noise generator instead and it reads about −17-17 dB. The spike stands much higher above the carpet on the analyzer, because the analyzer spreads the noise across 8,192 frequency slices while the tone lands in one.

PYTHON
import time

import numpy as np

_LAST = 0.0


def compute(ctx):
    global _LAST
    if time.monotonic() - _LAST < 1.0:
        return
    _LAST = time.monotonic()
    x = np.asarray(ctx.inputs[0])
    power = np.mean(np.abs(x) ** 2)
    if power == 0:
        print("Power: no signal")
        return
    print(f"Power: {10 * np.log10(power):.1f} dB")

Both generators must keep the same buffer size, data type, and sample rate, and the Spectrum Analyzer needs complex CF32 samples. The Quick Start covers the editor, the block catalog describes every block used here, and the Python block reference explains inputs, outputs, 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. M. Lichtman, "Frequency domain," PySDR: A Guide to SDR and DSP using Python. pysdr.org/content/frequency_domain.html ↩

CyberEther

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

Star on GitHub

© 2026 CyberEther. All rights reserved.

The final frontier!