ZigRadio Reference Manual

Generated from ZigRadio v0.10.0.

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

Core

Flowgraph

The Flowgraph type 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.

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});

Block

The Block type is the container for a ZigRadio block, holding its port names and types, implemented hooks, and sample rate.

ZigRadio blocks are implemented by wrapping a nested Block structure, which is initialized using introspection with the radio.Block.init(comptime BlockType: type) radio.Block constructor and providing a process() hook.

Example

const radio = @import("radio");

pub const MultiplyBlock = struct {
    block: radio.Block,

    pub fn init() MultiplyBlock {
        return .{ .block = radio.Block.init(@This()) };
    }

    pub fn process(_: *MultiplyBlock, x: []const f32, y: []const f32, z: []f32) !radio.ProcessResult {
        for (x, 0..) |_, i| {
            z[i] = x[i] * y[i];
        }

        return radio.ProcessResult.init(&[2]usize{ x.len, x.len }, &[1]usize{x.len});
    }
};

Process Hook

pub fn process(self: *BlockType, in1: []const f32, in2: []const f32, ..., out1: []std.math.Complex(f32), out2: []std.math.Complex(f32), ...) !radio.ProcessResult { ... }

The process() hook is the main function of the block, called by the framework repeatedly to convert input samples to output samples.

The framework deduces the input and output ports and their data types of a ZigRadio block from the type signature of the block's process() function. Arguments of a constant slice type map to input ports, while those of mutable slice type map to output ports. The process() function may access and manipulate the block's state through the self argument.

The ZigRadio framework guarantees that process() is only called when there are a non-zero amount of input samples available across all inputs, and at least as many output samples available, across all outputs. Blocks that need to produce more or less output samples relative to input samples are responsible for managing the available samples, which may require buffering them.

The return value of process() is a ProcessResult, which provides an accounting of how many samples were consumed and produced, allowing the framework to acknowledge input samples from upstream blocks and make output samples available to downstream blocks.

The process() function may also return an error, which will cause the block to terminate and the flow graph to collapse.

radio.ProcessResult.init(consumed: []const usize, produced: []const usize) ProcessResult

Construct a ProcessResult, where consumed contains the input samples consumed, and produced contains the output samples produced. The order of inputs in consumed and outputs in produced follow the order of inputs and outputs in the process() type signature, respectively.

Optional Hooks

Blocks may implement a few optional hooks called by the framework.

pub fn initialize(self: *Self, allocator: std.mem.Allocator) !void { ... }

The initialize() hook is used for memory allocation, I/O initialization, and sample rate dependent initialization. This function is called by the framework during flow graph setup, after all blocks are connected and their sample rates are determined.

The allocator passed to initialize() is the same one that the Flowgraph was initialized with. Blocks may call self.block.getRate(comptime T: type) T in initialize() to get their sample rate in terms of their preferred numeric type (e.g. f32, usize, etc.).

Blocks may return an error from initialize(), which will cause flow graph initialization to fail.

pub fn deinitialize(self: *Self, allocator: std.mem.Allocator) void { ... }

The deinitialize() hook is used for memory deallocation, I/O deinitialization, and other deinitialization. The function is called by the framework on flow graph teardown. The allocator passed to deinitialize() is the same as the one passed to initialize(), for convenience.

pub fn setRate(self: *Self, upstream_rate: f64) !f64 { ... }

The setRate() hook is used to override the block's sample rate. By default, blocks inherit the sample rate of the upstream block connected to their first input port. Blocks that produce samples at a different sample rate from their inputs (e.g. downsamplers, upsamplers, etc.), may implement their own setRate() which returns the modified sample rate. The upstream rate is passed in the upstream_rate argument.

Blocks may return an error from setRate(), which will cause flow graph initialization to fail.

Composite Block

The CompositeBlock type is the container for a ZigRadio block composition of blocks with internal connectivity and input/output ports at their boundary.

ZigRadio composite blocks are implemented by wrapping a nested CompositeBlock structure, which is initialized the init(comptime CompositeType: type, inputs: []const []const u8, outputs: []const []const u8) radio.CompositeBlock constructor and providing a connect() hook.

Example

const radio = @import("radio");

pub const MultiplyConstantAndSquareBlock = struct {
    block: radio.CompositeBlock,
    b1: radio.blocks.MultiplyConstantBlock,
    b2: radio.blocks.MultiplyBlock,

    pub fn init(constant: f32) MultiplyConstantAndSquareBlock {
        return .{
            .block = radio.CompositeBlock.init(@This(), &.{"in1"}, &.{"out1"}),
            .b1 = radio.blocks.MultiplyConstantBlock.init(constant),
            .b2 = radio.blocks.MultiplyBlock.init(),
        };
    }

    pub fn connect(self: *MultiplyConstantAndSquareBlock, flowgraph: *radio.Flowgraph) !void {
        // Internal connections
        try flowgraph.connectPort(&self.b1.block, "out1", &self.b2.block, "in1");
        try flowgraph.connectPort(&self.b1.block, "out1", &self.b2.block, "in2");

        // Alias inputs and outputs
        try flowgraph.alias(&self.block, "in1", &self.b1.block, "in1");
        try flowgraph.alias(&self.block, "out1", &self.b2.block, "out1");
    }
};

Connect Hook

pub fn connect(self: *CompsiteType, flowgraph: *Flowgraph) !void

The connect() hook is responsible for making internal connections and defining the boundary ports of the composition, stored in the parent flow graph.

Within connect(), the ordinary flow graph connect() and connectPort() APIs are used to make internal block connections, while the flow graph alias() API is used to alias a composite block's input or output port to an internal block's input or output port.

Composite blocks may return an error from connect(), which will cause flow graph initialization to fail.

Data Types

ZigRadio blocks use native Zig types for their input and output ports. Common data types include:

Custom Types

ZigRadio blocks can also use arbitrary struct and union types for inputs and outputs, like any other type. Custom types must implement the typeName() []const u8 getter for error reporting and debug logging by the framework.

pub const WeatherPacket = struct {
    temperature: i8 = 0,
    humidity: u8 = 0,
    wind_speed: u8 = 0,
    wind_direction: enum { N, E, S, W } = .N,

    pub fn typeName() []const u8 {
        return "WeatherPacket";
    }
};

Special Types

RefCounted(T: type) type

Create a reference-counted type wrapping an underlying type. Underlying type must implement init(...) T for element initialization on first reference, and deinit(self: *T) void for deinitialization on last unreference.

Asynchronous Control

Blocks can provide arbitrary functions to access or modify their state at runtime. These are normal functions, which are run exclusively of process() by the block runner thread, and thus require no special locking.

Block Example

const radio = @import("radio");

pub const MultiplyConstantBlock = struct {
    block: radio.Block,
    constant: f32,

    pub fn init(constant: f32) MultiplyConstantBlock {
        return .{ .block = radio.Block.init(@This()), .constant = constant };
    }

    pub fn process(self: *MultiplyConstantBlock, x: []const f32, z: []f32) !radio.ProcessResult {
        for (x, 0..) |_, i| {
            z[i] = x[i] * self.constant;
        }

        return radio.ProcessResult.init(&[1]usize{x.len}, &[1]usize{x.len});
    }

    pub fn setConstant(self: *MultiplyConstantBlock, constant: f32) !void {
        if (constant > 9000) return error.OutOfBounds;
        self.constant = constant;
    }

    pub fn getConstant(self: *MultiplyConstantBlock) f32 {
        return self.constant;
    }
};

This block exposes a setConstant() function to update its constant, and a getConstant() function to return it. These block functions can be called in a thread-safe manner through the flow graph call() API:

// Set Constant
try flowgraph.call(&multiplyconstant.block, MultiplyConstantBlock.setConstant, .{123});
// Get Constant
const constant = try flowgraph.call(&multiplyconstant.block, MultiplyConstantBlock.getConstant, .{});

Composite Block Example

Composite blocks also support asynchronous calls, but must nest calls into child blocks through the passed Flowgraph instance:

const radio = @import("radio");

pub const MultiplyConstantAndSquareBlock = struct {
    block: radio.CompositeBlock,
    b1: radio.blocks.MultiplyConstantBlock,
    b2: radio.blocks.MultiplyBlock,

    pub fn init(constant: f32) MultiplyConstantAndSquareBlock {
        return .{
            .block = radio.CompositeBlock.init(@This(), &.{"in1"}, &.{"out1"}),
            .b1 = radio.blocks.MultiplyConstantBlock.init(constant),
            .b2 = radio.blocks.MultiplyBlock.init(),
        };
    }

    pub fn connect(self: *MultiplyConstantAndSquareBlock, flowgraph: *radio.Flowgraph) !void {
        // Internal connections
        try flowgraph.connectPort(&self.b1.block, "out1", &self.b2.block, "in1");
        try flowgraph.connectPort(&self.b1.block, "out1", &self.b2.block, "in2");

        // Alias inputs and outputs
        try flowgraph.alias(&self.block, "in1", &self.b1.block, "in1");
        try flowgraph.alias(&self.block, "out1", &self.b2.block, "out1");
    }

    pub fn setConstant(self: *MultiplyConstantAndSquareBlock, flowgraph: *Flowgraph, constant: f32) !void {
        try flowgraph.call(&self.b1.block, MultiplyConstantBlock.setConstant, .{constant});
    }

    pub fn getConstant(self: *MultiplyConstantAndSquareBlock, flowgraph: *Flowgraph) !f32 {
        return try flowgraph.call(&self.b1.block, MultiplyConstantBlock.getConstant, .{});
    }
};

This composite block exposes a setConstant() function to update its constant, and a getConstant() function to return it. These block functions can be called in a thread-safe manner through the flow graph call() API:

// Set Constant
try flowgraph.call(&multiplyconstantandsquare.block, MultiplyConstantAndSquareBlock.setConstant, .{123});
// Get Constant
const constant = try flowgraph.call(&multiplyconstantandsquare.block, MultiplyConstantAndSquareBlock.getConstant, .{});

Note that nested asynchronous calls in a composite block will be executed sequentially, but not simultaneously, due to independent locking of each child block.

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)
    • device_serial (?u64, default null)
    • 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 a thread-safe interface for applications to push samples into the flowgraph.

API
wait(self: *Self, min_count: usize, timeout_ns: ?u64) error{ BrokenStream, Timeout }!void

Wait until a minimum number of samples are available for writing, with an optional timeout.

available(self: *Self) error{BrokenStream}!usize

Get the number of samples that can be written. Returns the BrokenStream error if the flow graph collapsed downstream.

get(self: *Self) []T

Get direct access to the write buffer.

update(self: *Self, count: usize) void

Advance the write buffer with the number of samples written.

write(self: *Self, samples: []const T) usize

Write a slice of samples and return the number of samples successfully written, which may be zero.

push(self: *Self, value: T) error{Unavailable}!void

Write a single sample, or return the Unavailable error if space was not available.

setEOS(self: *Self) void

Set the end-of-stream condition on the source, which will subsequently collapse the flow graph.

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 reader = input_file.reader(&buffer);
var src = radio.blocks.IQStreamSource.init(&reader.interface, .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 reader = input_file.reader(&buffer);
var src = radio.blocks.RealStreamSource.init(&reader.interface, .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)
    • device_serial (?[]const u8, default null)
    • 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);

WAVFileSource

Source one or more real-valued signals from a WAV file. The supported sample formats are 8-bit unsigned integer, 16-bit signed integer, and 32-bit signed integer.

radio.blocks.WAVFileSource.init(file: *std.fs.File, options: Options)
Arguments
  • file (*std.fs.File): File

  • options (Options): Additional options:

    • repeat_on_eof (bool, repeat on EOF, default false)
Type Signature
  • ❑➔ out1 f32, [out2 f32]
Example
// Source one channel WAV file
local src = radio.blocks.WAVFileSource(1).init(&input_file, .{});
try.top.connect(&src.block, &snk.block);

// Source two channel WAV file
local src = radio.blocks.WAVFileSource(2).init(&input_file, .{});
// Compose the two channels into a complex-valued signal
try top.connectPort(&src.block, "out1", &floattocomplex.block, "in1");
try top.connectPort(&src.block, "out2", &floattocomplex.block, "in2");
try.top.connect(&floattocomplex.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 a thread-safe interface for applications to consume samples from the flowgraph.

API
wait(self: *Self, min_count: usize, timeout_ns: ?u64) error{ EndOfStream, Timeout }!void

Wait until a minimum number of samples are available for reading, with an optional timeout.

available(self: *Self) error{EndOfStream}!usize

Get the number of samples that can be read. Returns the EndOfStream error if the flow graph collapsed upstream.

get(self: *Self) []const T

Get direct access to the read buffer.

update(self: *Self, count: usize) void

Advance the read buffer with the number of samples read.

read(self: *Self, samples: []T) usize

Read into a slice of samples and return the number of samples successfully read, which may be zero.

pop(self: *Self) ?T

Read a single sample, or return null if none was available.

discard(self: *Self) !void

Discard all available samples.

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 writer = output_file.writer(&buffer);
var snk = radio.blocks.IQStreamSink.init(&writer.interface, .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 writer = output_file.writer(&buffer);
var snk = radio.blocks.JSONStreamSink(Foo).init(&writer.interface, .{});
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 writer = output_file.writer(&buffer);
var snk = radio.blocks.RealStreamSink.init(&writer.interface, .u16be, .{});
try top.connect(&src.block, &snk.block);

WAVFileSink

Sink one or more real-valued signals to a WAV file. The supported sample formats are 8-bit unsigned integer, 16-bit signed integer, and 32-bit signed integer.

radio.blocks.WAVFileSink.init(file: *std.fs.File, options: Options)
Arguments
  • file (*std.fs.File): File

  • options (Options): Additional options

    • format (Format, choice of .u8, .s16, .s32, default .s16)
Type Signature
  • in1 f32, [in2 f32] ➔❑
Example
// Sink to a one channel WAV file
var snk = radio.blocks.WAVFileSink(1).init(&output_file, .{});
try top.connect(&src.block, &snk.block);

// Sink to a two channel WAV file
var snk = radio.blocks.WAVFileSink(2).init(&output_file, .{});
try top.connectPort(&src_left.block, "out", &snk.block, "in1");
try top.connectPort(&src_right.block, "out", &snk.block, "in2");

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);