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:
ZIGRADIO_DEBUG: Enable debug verbosityZIGRADIO_DISABLE_LIQUID: Disable liquid-dsp libraryZIGRADIO_DISABLE_VOLK: Disable volk libraryZIGRADIO_DISABLE_FFTW3F: Disable fftw3f library
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
- ❑➔
out1Complex(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
- ❑➔
out1T
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
- ❑➔
out1Complex(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
- ❑➔
out1f32
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
- ❑➔
out1Complex(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
- ❑➔
out1f32
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
- ❑➔
out1T
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
in1T ➔❑
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
in1T ➔❑
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
in1Complex(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 bystd.json.stringify()
Arguments
-
writer(*std.io.Writer): Writer -
options(Options): Additional options
Type Signature
in1T ➔❑
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 bystd.debug.print()
Type Signature
in1T ➔❑
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
in1f32,in2f32 ➔❑
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
in1f32 ➔❑
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
in1T ➔❑➔out1T
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
in1T ➔❑➔out1T
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
in1Complex(f32) ➔❑➔out1Complex(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
in1Complex(f32) ➔❑➔out1Complex(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
in1T ➔❑➔out1T
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
in1f32 ➔❑➔out1f32
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
in1T ➔❑➔out1T
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
in1T ➔❑➔out1T
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
in1T ➔❑➔out1T
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
in1f32 ➔❑➔out1f32
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
in1T ➔❑➔out1T
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
in1T ➔❑➔out1T
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
in1T,in2T ➔❑➔out1T
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
in1Complex(f32) ➔❑➔out1f32
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
in1T,in2T ➔❑➔out1T
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
in1Complex(f32),in2Complex(f32) ➔❑➔out1Complex(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
in1T,in2T ➔❑➔out1T
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
in1T ➔❑➔out1T
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
in1T ➔❑➔out1T
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
in1Complex(f32) ➔❑➔out1Complex(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
in1Complex(f32) ➔❑➔out1Complex(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
in1Complex(f32) ➔❑➔out1Complex(f32),out2f32
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
in1u1 ➔❑➔out1u1
Example
var diffdecoder = radio.blocks.DifferentialDecoderBlock(false).init();
SlicerBlock
Slice a signal into symbols using the specified slicer.
radio.blocks.SlicerBlock.init()
Type Signature
in1T ➔❑➔out1U
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
in1Complex(f32) ➔❑➔out1f32
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
in1Complex(f32) ➔❑➔out1f32
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
in1f32 ➔❑➔out1Complex(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
in1T ➔❑➔out1T
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
in1Complex(f32) ➔❑➔out1f32
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
in1Complex(f32) ➔❑➔out1f32
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
in1Complex(f32) ➔❑➔out1f32
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
in1Complex(f32) ➔❑➔out1f32
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
in1Complex(f32) ➔❑➔out1f32,out2f32
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
in1Complex(f32) ➔❑➔out1f32
Example
var demod = radio.blocks.FrequencyDiscriminatorBlock.init(5e3);