ZigRadio Reference Manual

Generated from ZigRadio v0.9.0-12-g76ce5a7.

Example

Wideband FM Broadcast Stereo Receiver
const std = @import("std");

const radio = @import("radio");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};

    const frequency = 91.1e6; // 91.1 MHz

    var source = radio.blocks.RtlSdrSource.init(frequency - 250e3, 960000, .{ .debug = true });
    var tuner = radio.blocks.TunerBlock.init(-250e3, 200e3, 4);
    var demodulator = radio.blocks.WBFMStereoDemodulatorBlock.init(.{});
    var l_af_downsampler = radio.blocks.DownsamplerBlock(f32).init(5);
    var r_af_downsampler = radio.blocks.DownsamplerBlock(f32).init(5);
    var sink = radio.blocks.PulseAudioSink(2).init();

    var top = radio.Flowgraph.init(gpa.allocator(), .{ .debug = true });
    defer top.deinit();
    try top.connect(&source.block, &tuner.block);
    try top.connect(&tuner.block, &demodulator.block);
    try top.connectPort(&demodulator.block, "out1", &l_af_downsampler.block, "in1");
    try top.connectPort(&demodulator.block, "out2", &r_af_downsampler.block, "in1");
    try top.connectPort(&l_af_downsampler.block, "out1", &sink.block, "in1");
    try top.connectPort(&r_af_downsampler.block, "out1", &sink.block, "in2");

    try top.start();
    radio.platform.waitForInterrupt();
    _ = try top.stop();
}

Building

Fetch the ZigRadio package:

zig fetch --save git+https://github.com/vsergeev/zigradio#master

Add ZigRadio as a dependency to your build.zig:

const radio = b.dependency("radio", .{});
...
exe.root_module.addImport("radio", radio.module("radio"));
exe.linkLibC();

Optimization ReleaseFast is recommended for real-time applications. libc is required for loading dynamic libraries used for acceleration and I/O.

Running

Acceleration

ZigRadio uses optional external libraries for acceleration, including VOLK, liquid-dsp, and FFTW. These libraries are automatically loaded at runtime, when available, and are recommended for real-time applications.

Environment Variables

Several environment variables control ZigRadio's runtime behavior, and can be enabled with truthy literals like 1, true, or yes:

For example, to enable debug verbosity:

$ ZIGRADIO_DEBUG=1 ./path/to/zigradio-program

To run a script with no external acceleration libraries:

$ ZIGRADIO_DISABLE_LIQUID=1 ZIGRADIO_DISABLE_VOLK=1 ZIGRADIO_DISABLE_FFTW3F=1 ./path/to/zigradio-program

Flowgraph

The Flowgraph is the top-level container for a ZigRadio flow graph.

radio.Flowgraph.init(allocator: std.mem.Allocator, options: Options) Flowgraph

Instantiate a flow graph with the provided allocator and options (struct { debug: bool = false }).

var gpa = std.heap.GeneralPurposeAllocator(.{}){};

var top = radio.Flowgraph.init(gpa.allocator(), .{ .debug = true });
defer top.deinit();
radio.Flowgraph.connect(self: *Flowgraph, src: anytype, dst: anytype) !void

Connect the first output port of src block to the first input port of dst block.

try top.connect(&src.block, &snk.block);
radio.Flowgraph.connectPort(self: *Flowgraph, src: anytype, src_port_name: []const u8, dst: anytype, dst_port_name: []const u8) !void

Connect the output port src_port_name of src block to the input port dst_port_name of dst block.

try top.connect(&l_filter.block, "out1", &sink.block, "in1");
try top.connect(&r_filter.block, "out1", &sink.block, "in2");
radio.Flowgraph.alias(self: *Flowgraph, composite: *CompositeBlock, port_name: []const u8, aliased_block: anytype, aliased_port_name: []const u8) !void

Alias the input or output port_name of composite block to the input or output port aliased_port_name of aliased_block block. Only used within the connect() hook of a composite block.

See the Creating Blocks guide for more information.

pub fn connect(self: *MyCompositeBlock, flowgraph: *Flowgraph) !void {
    ...
    try flowgraph.alias(&self.block, "in1", &self.b1.block, "in1");
    try flowgraph.alias(&self.block, "out1", &self.b2.block, "out1");
}
radio.Flowgraph.start(self: *Flowgraph) !void

Start the flow graph. This function does not block.

try top.start();
radio.Flowgraph.wait(self: *Flowgraph) !bool

Wait for the flow graph to terminate naturally. This function blocks. Returns true on success, or false for any block process failures.

bool success = try top.wait();
radio.Flowgraph.stop(self: *Flowgraph) !bool

Stop the flow graph by stopping any source blocks and then waiting for the flow graph to terminate naturally. This function blocks. Returns true on success, or false for any block process failures.

bool success = try top.stop();
radio.Flowgraph.run(self: *Flowgraph) !bool

Run the flow graph. This is equivalent to calling start() and then wait(). This function blocks.

bool success = try top.run();
radio.Flowgraph.call(self: *Flowgraph, block: anytype, comptime function: anytype, args: anytype) CallReturnType(function)

Make a thread-safe call into a block in the flow graph.

try top.call(&af_filter.block, radio.blocks.LowpassFilterBlock(f32, 128).setCutoff, .{5e3});

Blocks

Sources

AirspyHFSource

Source a complex-valued signal from an Airspy HF+. This source requires the libairspyhf library.

radio.blocks.AirspyHFSource.init(frequency: f64, rate: f64, options: Options)
Arguments
  • frequency (f64): Tuning frequency in Hz

  • rate (f64): Sample rate in Hz (e.g. 192 kHz, 256 kHz, 384 kHz, 768 kHz)

  • options (Options): Additional options:

    • hf_agc (bool, default true)
    • hf_agc_threshold (enum { Low, High}, default .Low)
    • hf_att (u8, default 0 dB, for manual attenuation when HF AGC is disabled, range of 0 to 48 dB, 6 dB step)
    • hf_lna (bool, default false)
    • debug (bool, default false)
Type Signature
  • ❑➔ out1 Complex(f32)
Example
var src = radio.blocks.AirspyHFSource.init(7.150e6, 192e3, .{ .hf_lna = true });
try top.connect(&src.block, &snk.block);

ApplicationSource

Source a signal from a host application.

Provides an interface for applications to push samples into the flowgraph.

radio.blocks.ApplicationSource(comptime T: type).init(rate: f64)
Comptime Arguments
  • T (type): Complex(f32), f32, u1, etc.
Arguments
  • rate (f64): Sample rate in Hz
Type Signature
  • ❑➔ out1 T
Example
var src = radio.blocks.ApplicationSource(std.math.Complex(f32)).init(1e6);
try top.connect(&src.block, &snk.block);
...
try src.push(std.math.Complex(f32).init(2, 3));

IQStreamSource

Source a complex-valued signal from a binary stream, using the specified sample format.

radio.blocks.IQStreamSource.init(reader: *std.io.Reader, format: SampleFormat, rate: f64, options: Options)
Arguments
  • reader (*std.io.Reader): Reader

  • format (SampleFormat): Choice of s8, u8, u16le, u16be, s16le, s16be, u32le, u32be, s32le, s32be, f32le, f32be, f64le, f64be

  • rate (f64): Sample rate in Hz

  • options (Options): Additional options

Type Signature
  • ❑➔ out1 Complex(f32)
Example
var input_file = try std.fs.cwd().openFile("samples.iq", .{});
defer input_file.close();
...
var src = radio.blocks.IQStreamSource.init(input_file.reader().any(), .s16le, 1e6, .{});
try top.connect(&src.block, &snk.block);

RealStreamSource

Source a real-valued signal from a binary stream, using the specified sample format.

radio.blocks.RealStreamSource.init(reader: std.io.AnyReader, format: SampleFormat, rate: f64, options: Options)
Arguments
  • reader (std.io.AnyReader): Reader

  • format (SampleFormat): Choice of s8, u8, u16le, u16be, s16le, s16be, u32le, u32be, s32le, s32be, f32le, f32be, f64le, f64be

  • rate (f64): Sample rate in Hz

  • options (Options): Additional options

Type Signature
  • ❑➔ out1 f32
Example
var input_file = try std.fs.cwd().openFile("samples.real", .{});
defer input_file.close();
...
var src = radio.blocks.RealStreamSource.init(input_file.reader().any(), .s16le, 1e6, .{});
try top.connect(&src.block, &snk.block);

RtlSdrSource

Source a complex-valued signal from an RTL-SDR dongle. This source requires the librtlsdr library.

radio.blocks.RtlSdrSource.init(frequency: f64, rate: f64, options: Options)
Arguments
  • frequency (f64): Tuning frequency in Hz

  • rate (f64): Sample rate in Hz

  • options (Options): Additional options: struct (bias_tee, direct_sampling, bandwidth, rf_gain, freq_correction, device_index, debug)

    • biastee (bool, default false)
    • direct_sampling (?DirectSamplingMode, default null, choice of .I, .Q)
    • bandwidth (?f32, default null for sample rate)
    • rf_gain (?f32, number in dB for manual gain, default null for auto-gain)
    • freq_correction (isize, in PPM, default 0)
    • device_index (isize, default 0)
    • debug (bool, default false)
Type Signature
  • ❑➔ out1 Complex(f32)
Example
var src = radio.blocks.RtlSdrSource.init(162.400e6, 1e6, .{ .autogain = true });
try top.connect(&src.block, &snk.block);

SignalSource

Sources a real-valued signal with configurable waveform, frequency, amplitude, and phase.

radio.blocks.SignalSource.init(waveform: WaveformFunction, frequency: f32, rate: f64, options: Options)
Arguments
  • waveform (WaveformFunction): Choice of .Cosine, .Sine, .Square, .Triangle, .Sawtooth, .Constant

  • frequency (f32): Frequency in Hz

  • rate (f64): Sample rate in Hz

  • options (Options): Additional options:

    • amplitude (f32, default 1.0)
    • offset (f32, default 0.0)
    • phase (f32, in radians, default 0.0)
Type Signature
  • ❑➔ out1 f32
Example
var src = radio.blocks.SignalSource.init(.Sine, 1000, 48000, .{ .amplitude = 0.5 });
try top.connect(&src.block, &snk.block);

ZeroSource

Source a zero-valued signal of the specified type.

radio.blocks.ZeroSource(comptime T: type).init(rate: f64)
Comptime Arguments
  • T (type): Complex(f32), f32, u1, etc.
Arguments
  • rate (f64): Sample rate in Hz
Type Signature
  • ❑➔ out1 T
Example
var src = radio.blocks.ZeroSource(std.math.Complex(f32)).init(1e6);
try top.connect(&src.block, &snk.block);

Sinks

ApplicationSink

Sink a signal to a host application.

Provides an interface for applications to consume samples from the flowgraph.

radio.blocks.ApplicationSink(comptime T: type).init()
Comptime Arguments
  • T (type): Complex(f32), f32, u1, etc.
Type Signature
  • in1 T ➔❑
Example
var snk = radio.blocks.ApplicationSink(std.math.Complex(f32)).init();
try top.connect(&src.block, &snk.block);
...
const sample = snk.pop();

BenchmarkSink

Benchmark throughput.

Consumes samples and periodically reports the sample rate and byte rate.

radio.blocks.BenchmarkSink(comptime T: type).init(options: Options)
Comptime Arguments
  • T (type): Complex(f32), f32, u1, etc.
Arguments
  • options (Options): Additional options:
    • title ([]const u8, default "BenchmarkSink")
    • report_period_ms (usize, reporting period in milliseconds, default 3000)
Type Signature
  • in1 T ➔❑
Example
var snk = radio.blocks.BenchmarkSink(std.math.Complex(f32)).init(.{});
try top.connect(&src.block, &snk.block);

IQStreamSink

Sink a complex-valued signal to a binary stream, using the specified sample format.

radio.blocks.IQStreamSink.init(writer: *std.io.Writer, format: SampleFormat, options: Options)
Arguments
  • writer (*std.io.Writer): Writer

  • format (SampleFormat): Choice of s8, u8, u16le, u16be, s16le, s16be, u32le, u32be, s32le, s32be, f32le, f32be, f64le, f64be

  • options (Options): Additional options

Type Signature
  • in1 Complex(f32) ➔❑
Example
var output_file = try std.fs.cwd().createFile("samples.iq", .{});
defer output_file.close();
...
var snk = radio.blocks.IQStreamSink.init(output_file.writer().any(), .s16le, .{});
try top.connect(&src.block, &snk.block);

JSONStreamSink

Sink a signal to a binary stream, serialized with JSON. Samples are serialized individually and newline delimited.

radio.blocks.JSONStreamSink(comptime T: type).init(writer: *std.io.Writer, options: Options)
Comptime Arguments
  • T (type): Any type serializable by std.json.stringify()
Arguments
  • writer (*std.io.Writer): Writer

  • options (Options): Additional options

Type Signature
  • in1 T ➔❑
Example
var output_file = try std.fs.cwd().createFile("samples.json", .{});
defer output_file.close();
...
var snk = radio.blocks.JSONStreamSink(Foo).init(output_file.writer().any(), .{});
try top.connect(&src.block, &snk.block);

PrintSink

Sink a signal to standard out.

radio.blocks.PrintSink(comptime T: type).init()
Comptime Arguments
  • T (type): Any type formattable by std.debug.print()
Type Signature
  • in1 T ➔❑
Example
var snk = radio.blocks.PrintSink(f32).init();
try top.connect(&src.block, &snk.block);

PulseAudioSink

Sink a mono or stereo real-valued signal to the PulseAudio sound server. This sink requires the libpulse-simple library.

radio.blocks.PulseAudioSink.init()
Type Signature
  • in1 f32, in2 f32 ➔❑
Example
var snk = radio.blocks.PulseAudioSink(2).init();
try top.connectPort(&src_left.block, "out", &snk.block, "in1");
try top.connectPort(&src_right.block, "out", &snk.block, "in2");

RealStreamSink

Sink a real-valued signal to a binary stream, using the specified sample format.

radio.blocks.RealStreamSink.init(writer: *std.io.Writer, format: SampleFormat, options: Options)
Arguments
  • writer (*std.io.Writer): Writer

  • format (SampleFormat): Choice of s8, u8, u16le, u16be, s16le, s16be, u32le, u32be, s32le, s32be, f32le, f32be, f64le, f64be

  • options (Options): Additional options

Type Signature
  • in1 f32 ➔❑
Example
var output_file = try std.fs.cwd().createFile("samples.real", .{});
defer output_file.close();
...
var snk = radio.blocks.RealStreamSink.init(output_file.writer().any(), .u16be, .{});
try top.connect(&src.block, &snk.block);

Filtering

BandpassFilterBlock

Filter a complex or real valued signal with a real-valued FIR band-pass filter generated by the window design method.

$$ y[n] = (x * h_{bpf})[n] $$

radio.blocks.BandpassFilterBlock(comptime T: type, comptime N: comptime_int).init(cutoffs: struct{f32,f32}, options: Options)
Comptime Arguments
  • T (type): Complex(f32), f32

  • N (comptime_int): Number of taps

Arguments
  • cutoffs (struct{f32,f32}): Lower and upper cutoff frequencies in Hz

  • options (Options): Additional options:

    • nyquist (?f32, alternate Nyquist frequency in Hz)
    • window (WindowFunction, window function, default Hamming)
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var filter = radio.blocks.BandpassFilterBlock(std.math.Complex(f32), 129).init(.{ 10e3, 20e3 }, .{});

BandstopFilterBlock

Filter a complex or real valued signal with a real-valued FIR band-stop filter generated by the window design method.

$$ y[n] = (x * h_{bsf})[n] $$

radio.blocks.BandstopFilterBlock(comptime T: type, comptime N: comptime_int).init(cutoffs: struct{f32,f32}, options: Options)
Comptime Arguments
  • T (type): Complex(f32), f32

  • N (comptime_int): Number of taps

Arguments
  • cutoffs (struct{f32,f32}): Lower and upper cutoff frequencies in Hz

  • options (Options): Additional options:

    • nyquist (?f32, alternate Nyquist frequency in Hz)
    • window (WindowFunction, window function, default Hamming)
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var filter = radio.blocks.BandstopFilterBlock(std.math.Complex(f32), 129).init(.{ 10e3, 20e3 }, .{});

ComplexBandpassFilterBlock

Filter a complex-valued signal with a complex-valued FIR band-pass filter generated by the window design method. This filter is asymmetric in the frequency domain.

$$ y[n] = (x * h_{bpf})[n] $$

radio.blocks.ComplexBandpassFilterBlock(comptime N: comptime_int).init(cutoffs: struct{f32,f32}, options: Options)
Comptime Arguments
  • N (comptime_int): Number of taps
Arguments
  • cutoffs (struct{f32,f32}): Lower and upper cutoff frequencies in Hz

  • options (Options): Additional options:

    • nyquist (?f32, alternate Nyquist frequency in Hz)
    • window (WindowFunction, window function, default Hamming)
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 Complex(f32)
Example
var filter = radio.blocks.ComplexBandpassFilterBlock(129).init(.{ 10e3, 20e3 }, .{});

ComplexBandstopFilterBlock

Filter a complex-valued signal with a complex-valued FIR band-stop filter generated by the window design method. This filter is asymmetric in the frequency domain.

$$ y[n] = (x * h_{bsf})[n] $$

radio.blocks.ComplexBandstopFilterBlock(comptime N: comptime_int).init(cutoffs: struct{f32,f32}, options: Options)
Comptime Arguments
  • N (comptime_int): Number of taps
Arguments
  • cutoffs (struct{f32,f32}): Lower and upper cutoff frequencies in Hz

  • options (Options): Additional options:

    • nyquist (?f32, alternate Nyquist frequency in Hz)
    • window (WindowFunction, window function, default Hamming)
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 Complex(f32)
Example
var filter = radio.blocks.ComplexBandstopFilterBlock(129).init(.{ 10e3, 20e3 }, .{});

FIRFilterBlock

Filter a complex or real valued signal with an FIR filter.

$$ y[n] = (x * h)[n] $$

$$ y[n] = b_0 x[n] + b_1 x[n-1] + ... + b_N x[n-N] $$

radio.blocks.FIRFilterBlock(comptime T: type, comptime U: type, comptime N: comptime_int).init(taps: [N]U)
Comptime Arguments
  • T (type): Complex(f32), f32

  • U (type): Tap data type (e.g. Complex(f32), f32)

  • N (comptime_int): Number of taps

Arguments
  • taps ([N]U): Taps
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var filter = radio.blocks.FIRFilterBlock(std.math.Complex(f32), f32, 64).init(taps);

FMDeemphasisFilterBlock

Filter a complex or real valued signal with an FM De-emphasis filter, a single-pole low-pass IIR filter.

$$ y[n] = (x * h_{fmdeemph})[n] $$

radio.blocks.FMDeemphasisFilterBlock.init(tau: De-emphasis)
Arguments
  • tau (De-emphasis): time constant
Type Signature
  • in1 f32 ➔❑➔ out1 f32
Example
var deemphasis = radio.blocks.FMDeemphasisFilterBlock.init(75e-6);

HighpassFilterBlock

Filter a complex or real valued signal with a real-valued FIR high-pass filter generated by the window design method.

$$ y[n] = (x * h_{hpf})[n] $$

radio.blocks.HighpassFilterBlock(comptime T: type, comptime N: comptime_int).init(cutoff: f32, options: Options)
Comptime Arguments
  • T (type): Complex(f32), f32

  • N (comptime_int): Number of taps (filter length)

Arguments
  • cutoff (f32): Cutoff frequency in Hz

  • options (Options): Additional options:

    • nyquist (?f32, alternate Nyquist frequency in Hz)
    • window (WindowFunction, window function, default Hamming)
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var filter = radio.blocks.HighpassFilterBlock(std.math.Complex(f32), 129).init(100, .{});

IIRFilterBlock

Filter a complex or real valued signal with an IIR filter.

$$ y[n] = (x * h)[n] $$

$$ \begin{align} y[n] = &\frac{1}{a_0}(b_0 x[n] + b_1 x[n-1] + ... + b_N x[n-N] \\ - &a_1 y[n-1] - a_2 y[n-2] - ... - a_M x[n-M])\end{align} $$

radio.blocks.IIRFilterBlock(comptime T: type, comptime N: comptime_int, comptime M: comptime_int).init(b_taps: [N]f32, a_taps: [M]f32)
Comptime Arguments
  • T (type): Complex(f32), f32

  • N (comptime_int): Number of feedforward taps

  • M (comptime_int): Number of feedback taps

Arguments
  • b_taps ([N]f32): Feedforward taps

  • a_taps ([M]f32): Feedback taps

Type Signature
  • in1 T ➔❑➔ out1 T
Example
var filter = radio.blocks.IIRFilterBlock(std.math.Complex(f32), 3, 3).init(b_taps, a_taps);

LowpassFilterBlock

Filter a complex or real valued signal with a real-valued FIR low-pass filter generated by the window design method.

$$ y[n] = (x * h_{lpf})[n] $$

radio.blocks.LowpassFilterBlock(comptime T: type, comptime N: comptime_int).init(cutoff: f32, options: Options)
Comptime Arguments
  • T (type): Complex(f32), f32

  • N (comptime_int): Number of taps (filter length)

Arguments
  • cutoff (f32): Cutoff frequency in Hz

  • options (Options): Additional options:

    • nyquist (?f32, alternate Nyquist frequency in Hz)
    • window (WindowFunction, window function, default Hamming)
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var filter = radio.blocks.LowpassFilterBlock(std.math.Complex(f32), 128).init(10e3, .{});

RectangularMatchedFilterBlock

Correlate a real-valued signal with a rectangular matched filter.

radio.blocks.RectangularMatchedFilterBlock.init(baudrate: Baudrate)
Arguments
  • baudrate (Baudrate):
Type Signature
  • in1 f32 ➔❑➔ out1 f32
Example
var matched_filter = radio.blocks.RectangularMatchedFilterBlock.init(2400);

SinglepoleHighpassFilterBlock

Filter a complex or real valued signal with a single-pole high-pass IIR filter.

$$ H(s) = \frac{\tau s}{\tau s + 1} $$

$$ H(z) = \frac{2\tau f_s}{1 + 2\tau f_s} \frac{1 - z^{-1}}{1 + (\frac{1 - 2\tau f_s}{1 + 2\tau f_s}) z^{-1}} $$

$$ y[n] = \frac{2\tau f_s}{1 + 2\tau f_s} \; x[n] + \frac{2\tau f_s}{1 + 2\tau f_s} \; x[n-1] - \frac{1 - 2\tau f_s}{1 + 2\tau f_s} \; y[n-1] $$

radio.blocks.SinglepoleHighpassFilterBlock(comptime T: type).init(cutoff: f32)
Comptime Arguments
  • T (type): Complex(f32), f32
Arguments
  • cutoff (f32): Cutoff frequency in Hz
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var filter = radio.blocks.SinglepoleHighpassFilterBlock(f32).init(100);

SinglepoleLowpassFilterBlock

Filter a complex or real valued signal with a single-pole low-pass IIR filter.

$$ H(s) = \frac{1}{\tau s + 1} $$

$$ H(z) = \frac{1}{1 + 2\tau f_s} \frac{1 + z^{-1}}{1 + (\frac{1 - 2\tau f_s}{1 + 2\tau f_s}) z^{-1}} $$

$$ y[n] = \frac{1}{1 + 2\tau f_s} \; x[n] + \frac{1}{1 + 2\tau f_s} \; x[n-1] - \frac{1 - 2\tau f_s}{1 + 2\tau f_s} \; y[n-1] $$

radio.blocks.SinglepoleLowpassFilterBlock(comptime T: type).init(cutoff: f32)
Comptime Arguments
  • T (type): Complex(f32), f32
Arguments
  • cutoff (f32): Cutoff frequency in Hz
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var filter = radio.blocks.SinglepoleLowpassFilterBlock(f32).init(1e3);

Math Operations

AddBlock

Add two signals.

$$ y[n] = x_{1}[n] + x_{2}[n] $$

radio.blocks.AddBlock(comptime T: type).init()
Comptime Arguments
  • T (type): Complex(f32), f32, etc.
Type Signature
  • in1 T, in2 T ➔❑➔ out1 T
Example
var summer = radio.blocks.AddBlock(std.math.Complex(f32)).init();
try top.connectPort(&src1.block, "out1", &summer.block, "in1");
try top.connectPort(&src2.block, "out1", &summer.block, "in2");
try top.connect(&summer.block, &sink.block);

ComplexMagnitudeBlock

Compute the magnitude of a complex-valued signal.

$$ y[n] = |x[n]| $$

$$ y[n] = \sqrt{\text{Re}(x[n])^2 + \text{Im}(x[n])^2} $$

radio.blocks.ComplexMagnitudeBlock.init()
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32
Example
var mag = radio.blocks.ComplexMagnitudeBlock.init();

MultiplyBlock

Multiply two signals.

$$ y[n] = x_{1}[n] \; x_{2}[n] $$

radio.blocks.MultiplyBlock(comptime T: type).init()
Comptime Arguments
  • T (type): Complex(f32), f32, etc.
Type Signature
  • in1 T, in2 T ➔❑➔ out1 T
Example
var multiplier = radio.blocks.MultiplyBlock(std.math.Complex(f32)).init();
try top.connectPort(&src1.block, "out1", &multiplier.block, "in1");
try top.connectPort(&src2.block, "out1", &multiplier.block, "in2");
try top.connect(&multiplier.block, &snk.block);

MultiplyConjugateBlock

Multiply a complex-valued signal by the complex conjugate of another complex-valued signal.

$$ y[n] = x_{1}[n] \; x_{2}^*[n] $$

radio.blocks.MultiplyConjugateBlock.init()
Type Signature
  • in1 Complex(f32), in2 Complex(f32) ➔❑➔ out1 Complex(f32)
Example
var mixer = radio.blocks.MultiplyConjugateBlock.init();
try top.connectPort(&src1.block, "out1", &mixer.block, "in1");
try top.connectPort(&src2.block, "out1", &mixer.block, "in2");
try top.connect(&mixer.block, &snk.block);

SubtractBlock

Subtract two signals.

$$ y[n] = x_{1}[n] - x_{2}[n] $$

radio.blocks.SubtractBlock(comptime T: type).init()
Comptime Arguments
  • T (type): Complex(f32), f32, etc.
Type Signature
  • in1 T, in2 T ➔❑➔ out1 T
Example
var subtractor = radio.blocks.SubtractBlock(std.math.Complex(f32)).init();
try top.connectPort(&src1.block, "out1", &subtractor.block, "in1");
try top.connectPort(&src1.block, "out1", &subtractor.block, "in2");
try top.connect(&subtractor.block, &snk.block);

Level Control

AGCBlock

Apply automatic gain to a real or complex valued signal to maintain an average target power.

$$ y[n] = \text{AGC}(x[n], \text{mode}, \text{target}, \text{threshold}) $$

Implementation note: this is a feedforward AGC. The power_tau time constant controls the moving average of the power estimator. The gain_tau time constant controls the speed of the gain adjustment. The gain has symmetric attack and decay dynamics.

radio.blocks.AGCBlock(comptime T: type).init(mode: Mode, options: Options)
Comptime Arguments
  • T (type): Complex(f32), f32
Arguments
  • mode (Mode): AGC mode, either preset of .Slow, .Medium, .Fast or a custom time constant

  • options (Options): Additional options:

    • target_dbfs (f32, target level in dBFS, default -20)
    • threshold_dbfs (f32, threshold level in dBFS, default -75)
    • power_tau (f32, power estimator time constant, default 1.0)
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var agc = radio.blocks.AGCBlock(std.math.Complex(f32)).init(.{ .preset = .Fast }, .{ .target_dbfs = -30, .threshold_dbfs = -75 });

Sample Rate Manipulation

DownsamplerBlock

Downsample a complex or real valued signal. This block reduces the sample rate for downstream blocks in the flow graph by a factor of M.

$$ y[n] = x[nM] $$

Note: this block performs no anti-alias filtering.

radio.blocks.DownsamplerBlock(comptime T: type).init(factor: usize)
Comptime Arguments
  • T (type): Complex(f32), f32, etc.
Arguments
  • factor (usize): Downsampling factor
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var downsampler = radio.blocks.DownsamplerBlock(std.math.Complex(f32)).init(4);

Spectrum Manipulation

TunerBlock

Frequency translate, low-pass filter, and downsample a complex-valued signal.

$$ y[n] = (\text{FrequencyTranslate}(x[n], f_{offset}) * h_{lpf})[nM] $$

This block is convenient for translating signals to baseband.

radio.blocks.TunerBlock.init(offset: f32, bandwidth: f32, factor: usize)
Arguments
  • offset (f32): Translation offset in Hz

  • bandwidth (f32): Signal bandwidth in Hz

  • factor (usize): Downsampling factor M

Type Signature
  • in1 Complex(f32) ➔❑➔ out1 Complex(f32)
Example
var tuner = radio.blocks.TunerBlock.init(50e3, 10e3, 5);
try top.connect(&src.block, &tuner.block);
try top.connect(&tuner.block, &snk.block);

FrequencyTranslatorBlock

Frequency translate a complex-valued signal by mixing it with \( e^{j \omega_0 n} \), where \( \omega_0 = 2 \pi f_o / f_s \).

$$ y[n] = x[n] \; e^{j\omega_0 n} $$

radio.blocks.FrequencyTranslatorBlock.init(offset: Translation)
Arguments
  • offset (Translation): offset in Hz
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 Complex(f32)
Example
var translator = radio.blocks.FrequencyTranslatorBlock.init(-50e3);

Carrier and Clock Recovery

ComplexPLLBlock

Generate a phase-locked complex sinusoid to a complex-valued reference signal.

$$ y[n] = \text{PLL}(x[n], f_{BW}, f_{min}, f_{max}, M) $$

radio.blocks.ComplexPLLBlock.init(loop_bandwidth: f32, frequency_range: struct{f32,f32}, options: Options)
Arguments
  • loop_bandwidth (f32): Loop bandwidth in Hz

  • frequency_range (struct{f32,f32}): Minimum and maximum frequency range in Hz

  • options (Options): Additional options:

    • multiplier (f32, frequency multiplier, default 1.0)
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 Complex(f32), out2 f32
Example
var pll = radio.blocks.ComplexPLLBlock.init(500, .{ 8e3, 12e3 }, .{});
try top.connect(&src.block, &pll.block);
try top.connectPort(&pll.block, "out1", &snk.block);
try top.connectPort(&pll.block, "out2", &err_snk.block);

Digital

DifferentialDecoderBlock

Decode a differentially encoded bit stream.

radio.blocks.DifferentialDecoderBlock.init()
Type Signature
  • in1 u1 ➔❑➔ out1 u1
Example
var diffdecoder = radio.blocks.DifferentialDecoderBlock(false).init();

SlicerBlock

Slice a signal into symbols using the specified slicer.

radio.blocks.SlicerBlock.init()
Type Signature
  • in1 T ➔❑➔ out1 U
Example
var slicer = radio.blocks.SlicerBlock(radio.blocks.BinarySlicer).init();

Type Conversion

ComplexToImagBlock

Decompose the imaginary part of a complex-valued signal.

$$ y[n] = \text{Im}(x[n]) $$

radio.blocks.ComplexToImagBlock.init()
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32
Example
var complextoimag = radio.blocks.ComplexToImagBlock.init();

ComplexToRealBlock

Decompose the real part of a complex-valued signal.

$$ y[n] = \text{Re}(x[n]) $$

radio.blocks.ComplexToRealBlock.init()
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32
Example
var complextoreal = radio.blocks.ComplexToRealBlock.init();

RealToComplexBlock

Compose a complex-valued signal from a real-valued signal and a zero-valued imaginary part.

$$ y[n] = x[n] + 0 \, j $$

radio.blocks.RealToComplexBlock.init()
Type Signature
  • in1 f32 ➔❑➔ out1 Complex(f32)
Example
var realtocomplex = radio.blocks.RealToComplexBlock.init();

Miscellaneous

DelayBlock

Delay a signal by a fixed number of samples.

$$ y[n] = x[n - D] $$

radio.blocks.DelayBlock(comptime T: type).init(delay: usize)
Comptime Arguments
  • T (type): Complex(f32), f32, etc.
Arguments
  • delay (usize): Number of samples to delay
Type Signature
  • in1 T ➔❑➔ out1 T
Example
var delay = radio.blocks.DelayBlock(std.math.Complex(f32)).init(7);

Demodulation

AMEnvelopeDemodulatorBlock

Demodulate a baseband, double-sideband amplitude modulated

$$ y[n] = \text{AMDemodulate}(x[n], \text{bandwidth}) $$

complex-valued signal with an envelope detector.

radio.blocks.AMEnvelopeDemodulatorBlock.init(options: Options)
Arguments
  • options (Options): Additional options:
    • bandwidth (f32, bandwidth in Hz, default 5e3)
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32
Example
var demod = radio.blocks.AMEnvelopeDemodulatorBlock.init(.{});
try top.connect(&src.block, &demod.block);
try top.connect(&demod.block, &snk.block);

AMSynchronousDemodulatorBlock

Demodulate a baseband, double-sideband amplitude modulated complex-valued signal with a synchronous detector.

$$ y[n] = \text{AMDemodulate}(x[n], \text{bandwidth}) $$

radio.blocks.AMSynchronousDemodulatorBlock.init(options: Options)
Arguments
  • options (Options): Additional options:
    • bandwidth (f32, bandwidth in Hz, default 5e3)
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32
Example
var demod = radio.blocks.AMSynchronousDemodulatorBlock.init(.{});
try top.connect(&src.block, &demod.block);
try top.connect(&demod.block, &snk.block);

NBFMDemodulatorBlock

Demodulate a baseband, narrowband FM modulated complex-valued signal.

$$ y[n] = \text{NBFMDemodulate}(x[n], \text{deviation}, \text{bandwidth}) $$

radio.blocks.NBFMDemodulatorBlock.init(options: Options)
Arguments
  • options (Options): Additional options:
    • bandwidth (f32, bandwidth in Hz, default 5e3)
    • deviation (f32, deviation in Hz, default 4e3)
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32
Example
var demod = radio.blocks.NBFMDemodulatorBlock.init(.{});
try top.connect(&src.block, &demod.block);
try top.connect(&demod.block, &snk.block);

WBFMMonoDemodulatorBlock

Demodulate a baseband, broadcast radio wideband FM modulated complex-valued signal into the real-valued mono channel (L+R) signal.

$$ y[n] = \text{WBFMMonoDemodulate}(x[n], \text{deviation}, \text{bandwidth}, \tau) $$

radio.blocks.WBFMMonoDemodulatorBlock.init(options: Options)
Arguments
  • options (Options): Additional options:
    • deviation (f32, deviation in Hz, default 75e3)
    • af_bandwidth (f32, audio bandwidth in Hz, default 15e3)
    • af_deemphasis_tau (f32, audio de-emphasis time constant, default 75e-6)
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32
Example
var demod = radio.blocks.WBFMMonoDemodulatorBlock.init(.{});
try top.connect(&src.block, &demod.block);
try top.connect(&demod.block, &snk.block);

WBFMStereoDemodulatorBlock

Demodulate a baseband, broadcast radio wideband FM modulated complex-valued signal into the real-valued stereo channel (L and R) signals.

$$ y_{left}[n], y_{right}[n] = \text{WBFMStereoDemodulate}(x[n], \text{deviation}, \text{bandwidth}, \tau) $$

radio.blocks.WBFMStereoDemodulatorBlock.init(options: Options)
Arguments
  • options (Options): Additional options:
    • deviation (f32, deviation in Hz, default 75e3)
    • af_bandwidth (f32, audio bandwidth in Hz, default 15e3)
    • af_deemphasis_tau (f32, audio de-emphasis time constant, default 75e-6)
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32, out2 f32
Example
var demod = radio.blocks.WBFMStereoDemodulatorBlock.init(.{});
try top.connect(&src.block, &demod.block);
try top.connectPort(&demod.block, "out1", &left_snk.block, "in1");
try top.connectPort(&demod.block, "out2", &right_snk.block, "in1");

FrequencyDiscriminatorBlock

Compute the instantaneous frequency of a complex-valued input signal. This is a method of frequency demodulation.

$$ y[n] = \frac{\text{arg}(x[n] \; x^*[n-1])}{2 \pi k} $$

radio.blocks.FrequencyDiscriminatorBlock.init(deviation: f32)
Arguments
  • deviation (f32): Frequency deviation in Hz
Type Signature
  • in1 Complex(f32) ➔❑➔ out1 f32
Example
var demod = radio.blocks.FrequencyDiscriminatorBlock.init(5e3);