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:
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
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:
std.math.Complex(f32), for complex-valued samplesf32, for real-valued samplesu8, for byte samplesu1, for bit samples
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
- ❑➔
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 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
- ❑➔
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 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
- ❑➔
out1f32
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
- ❑➔
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);
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
- ❑➔
out1f32,[out2f32]
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
- ❑➔
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 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
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 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 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 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 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 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 optionsformat(Format, choice of.u8,.s16,.s32, default.s16)
Type Signature
in1f32,[in2f32] ➔❑
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
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);