stream

package
v0.0.0-...-9809d57 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2023 License: MIT Imports: 11 Imported by: 3

README

hz.tools/sdr/stream

The stream package implements functions on top of the generic sdr.Reader and sdr.Writer interface to do things like sample format conversion.

Documentation

Overview

Package stream contains a number of helpers to process sdr.Reader or sdr.Writers. A lot of this code is particularly fiddly to implement in leaf packages, or is so widely useful it's worth giving to everyone.

Index

Constants

This section is empty.

Variables

View Source
var (

	// ErrRingBufferUnderrun will be returned if a Read operation on a
	// Ring Buffer catches up to the Write head. This can be a temporary
	// state, and subsequent Read operations will be error free if a
	// Slot is written to.
	//
	// This error is only returned if BlockReads is set to False.
	ErrRingBufferUnderrun = fmt.Errorf("RingBuffer: Buffer Underrun")
)

Functions

func Add

func Add(readers ...sdr.Reader) (sdr.Reader, error)

Add will take any number of Readers, and mix all those Readers into a single Reader. This is likely not generally useful for generating data to transmit, but could be very useful for testing.

Each of the readers must be the same SampleFormat and SampleRate or an error will be returned.

The samples, when added must not exceed +1 or go below -1. This causes clipping and clipping is bad. If you're adding two waveforms, be absolutely sure you've adjusted the gain correctly on the readers with the stream.Gain reader.

func BeamformAngles

func BeamformAngles(
	frequency rf.Hz,
	angle float64,
	distances []float64,
) []complex64

BeamformAngles will determine what the phase offset required by the Beamform Reader.

  • frequency is the *rf* frequency (not IF).
  • angle is in *degrees*
  • distances is in *meters*

func BeamformAngles2D

func BeamformAngles2D(
	frequency rf.Hz,
	angle float64,
	center [2]float64,
	antennas [][2]float64,
) []complex64

BeamformAngles2D will determine what the phase offset required by the Beamform Reader.

  • frequency is the *rf* frequency (not IF).
  • angle is in *degrees*
  • ref is the X/Y coordinate in meters of the 'center' (can be anywhere).
  • antennas are X/Y coordinates in *meters*

func ConvertReader

func ConvertReader(in sdr.Reader, to sdr.SampleFormat) (sdr.Reader, error)

ConvertReader will convert one reader in a specific SampleFormat to a reader of another format.

This can be used is code is expecting to be reading from a sdr.Reader and requires that the IQ samples be in a specific format. Practically, this lets the code read from an rtl-sdr or hackrf in uint8 format, and get samples in complex64, or vice-versa (read from a complex64 capture and process as if it was an rtl-sdr).

func ConvertWriter

func ConvertWriter(
	out sdr.Writer,
	inputFormat sdr.SampleFormat,
) (sdr.Writer, error)

ConvertWriter will convert one writer of a specific type to a writer of another SampleFormat.

ConvertWriter will take the output Writer (out) and create a new writer with a new sample format (inputFormat).

func ConvolutionReader

func ConvolutionReader(
	r sdr.Reader,
	planner fft.Planner,
	filter []complex64,
) (sdr.Reader, error)

ConvolutionReader will perform an fft against a window of samples, and multiply those sampls in frequency-space against the provided window.

This can do things like apply a filter, etc. The fun really is endless.

The `filter` slice is expected to be in the frequency domain, not time domain. This should *not* be a sdr.SamplesC64, it will yield absurd results.

func DecimateBuffer

func DecimateBuffer(to, from sdr.Samples, factor uint, offset int) (int, error)

DecimateBuffer will take every Nth sample, reducing the number of samples per second on the other end by the same factor.

This is sometimes also called "Downsamping" or "Compression", but a lot of other tools use the term decimation, even though it's not always a downsample of a factor of 100.

func DecimateReader

func DecimateReader(in sdr.Reader, factor uint) (sdr.Reader, error)

DecimateReader will take even Nth sample (where N is the `factor` argument) from an sdr.Reader, and provide the downsampled or compressed iq stream through the returned Reader.

This will reduce the sample rate by the provided factor (so if the input Reader is at 18 Msps, and we apply a factor of 10 Decimation, we'll get an output Reader of 1.8 Msps.

func DownsampleBuffer

func DownsampleBuffer(to, from sdr.Samples, factor uint, offset int) (int, error)

DownsampleBuffer will take an oversampled input buffer, and write the downsampled output samples to the target buffer.

func DownsampleReader

func DownsampleReader(in sdr.Reader, factor uint) (sdr.Reader, error)

DownsampleReader will take an oversampled input stream, and create a downsampled output stream that increases the ENOB (effective number of bits).

For every 4 samples downsampled, you gain the equivalent of one bit of precision. For instance, for an 8-bit ADC at 3 Megasamples per secnd being downsampled, the following table outlines the ENOB and sample rate depending on the factor.

+---------+-------------+------+
| factor  | sample rate | ENOB |
+---------+-------------+------+
|      4  |      750000 |    9 |
+---------+-------------+------+
|      16 |      187500 |   10 |
+---------+-------------+------+
|      64 |       46875 |   11 |
+---------+-------------+------+
|     256 |       11718 |   12 |
+---------+-------------+------+

func Gain

func Gain(r sdr.Reader, v float32) sdr.Reader

Gain will apply a gain scaler to all iq samples in the reader, as the values are read.

func Multiply

func Multiply(r sdr.Reader, m complex64) (sdr.Reader, error)

Multiply will multiply each iq sample by the value m. This will 'rotate' each sample by the defined amount.

func Noise

func Noise(nc NoiseConfig) sdr.Reader

Noise will generate a sdr.Reader of gaussian noise. The params controlling the range and distribution of the noise are passed in via NoiseConfig. If no values are set, "sensible" defaults are created.

This function is intended for debugging and development - production use should be carefully considered, and likely trigger a conversation with the maintainers regarding your use-case and how to ensure this function is correct.

func NoisyReader

func NoisyReader(nc NoiseConfig, r sdr.Reader, snr float32) (sdr.Reader, error)

NoisyReader will pass along data from an underlying reader but with some added noise on top.

SNR represents the signal to noise ratio. The SNR param is a float that represents the amount of noise added to the provided signal from 1 (no noise) to 0 (100% noise).

func ReadTransformer

func ReadTransformer(in sdr.Reader, config ReadTransformerConfig) (sdr.Reader, error)

ReadTransformer will wrap an sdr.Pipe and run the provided Processor over each chunk as available.

This code will spawn a single Goroutine for each invocation who's lifetime is tied to the provided Reader. Be sure to close the reader or this code will remain alive forever!

func ShiftBuffer

func ShiftBuffer(sampleRate uint) func(rf.Hz, sdr.SamplesC64)

ShiftBuffer will return a function that will track phase to shift consecutive buffers.

func ShiftReader

func ShiftReader(r sdr.Reader, shift rf.Hz) (sdr.Reader, error)

ShiftReader will shift the iq samples by the target frequency. So a carrier at the provided shift frequency offset will be read through at DC.

func StandbyReader

func StandbyReader(rx sdr.Receiver) (sdr.ReadCloser, error)

StandbyReader is a reusable ReadCloser which wraps an sdr.Receiver When IQ data is read from the Reader, it will StartRx, and read samples to the new underlying ReadCloser from StartRx. When "Close" is called, the underlying ReadCloser will be closed, but the StandbyReader remains usable.

This enables easier management of the Receiver; less work needs to go into management of the state of the Receiver. Beware, this doesn't mean *no* management, just easier management.

The underlying sdr.ReadCloser's SampleFormat is the SampleFormat returned by the Receiver, and the SampleRate is the SampleRate as read at the time on construction - and must not change.

func StandbyWriter

func StandbyWriter(tx sdr.Transmitter) (sdr.WriteCloser, error)

StandbyWriter is a reusable WriteCloser which wraps an sdr.Transmitter. When IQ data is written to the Writer, it will StartTx, and write samples to the new underlying WriteCloser from StartTx. When "Close" is called, the underlying WriteCloser will be closed, but the StandbyWriter remains usable.

This enables easier management of the Transmitter; less work needs to go into management of the state of the Transmitter. Beware, this doesn't mean *no* management, just easier management.

The underlying sdr.WriteCloser's SampleFormat is the SampleFormat returned by the Transmitter, and the SampleRate is the SampleRate as read at the time on construction - and must not change.

func Throttle

func Throttle(r sdr.Reader) (sdr.Reader, error)

Throttle will read the sdr.Reader's SampleRate, and throttle the stream to play the Reader back "real time".

func ThrottleSecondsPerSecond

func ThrottleSecondsPerSecond(r sdr.Reader, d time.Duration) (sdr.Reader, error)

ThrottleSecondsPerSecond will read the sdr.Reader's SampleRate, and throttle the stream to play the Reader back where the duration 'd' passes every second.

Types

type Beamform

type Beamform struct {
	sdr.Reader
	// contains filtered or unexported fields
}

Beamform will combine a set of sdr.Readers into a single sdr.Reader using the provided phase angles to stear the beam.

func ReadBeamform

func ReadBeamform(rs sdr.Readers, cfg BeamformConfig) (*Beamform, error)

ReadBeamform will create a new sdr.Reader from a series of coherent sdr.Readers using the provided phase angles.

func (*Beamform) SetPhaseAngles

func (b *Beamform) SetPhaseAngles(angles []complex64) error

SetPhaseAngles will set the phase angle to shift every stream by.

type BeamformConfig

type BeamformConfig struct {
	Angles []complex64
}

BeamformConfig contains configuration for the combined samples.

type BufPipe2

type BufPipe2 struct {
	// contains filtered or unexported fields
}

BufPipe2 is a new (more stable?) and experimental implementation of a buffered sdr.Pipe

func NewBufPipe2

func NewBufPipe2(capacity int, sampleRate uint, sampleFormat sdr.SampleFormat) (*BufPipe2, error)

NewBufPipe2 will create a new stream.BufPipe, which wraps a normal sdr.Pipe, but writes will not block.

func (*BufPipe2) Close

func (p *BufPipe2) Close() error

Close will close the BufPipe, which will remain readable until the end of buffered data.

func (*BufPipe2) CloseWithError

func (p *BufPipe2) CloseWithError(err error) error

CloseWithError will close the pipe, and return the provided error on any subsequent call.

func (*BufPipe2) Read

func (p *BufPipe2) Read(s sdr.Samples) (int, error)

Read implements the sdr.ReadWriter interface.

func (*BufPipe2) SampleFormat

func (p *BufPipe2) SampleFormat() sdr.SampleFormat

SampleFormat implements the sdr.ReadWriter interface.

func (*BufPipe2) SampleRate

func (p *BufPipe2) SampleRate() uint

SampleRate implements the sdr.ReadWriter interface.

func (*BufPipe2) Write

func (p *BufPipe2) Write(s sdr.Samples) (int, error)

Write implements the sdr.ReadWriter interface.

type NoiseConfig

type NoiseConfig struct {
	// Source will provide a RNG for use by the Noise reader to generate
	// normally distributed noise.
	Source rand.Source

	// SampleRate is a *required* param if any code will be consuming this
	// reader. This will (unhelpfully!) default to 0, which is a massive
	// consistency issue. This value should be set to something sensible!
	SampleRate uint

	// StandardDeviation will control how wide the Standard Deviation of values
	// produced by this function is.
	//
	// If left at 0, this will be set to a sensible value for normal amounts
	// of noise encountered during real life.
	StandardDeviation float64
}

NoiseConfig will dictate the type of noise generated by the Noise reader.

type ReadTransformerConfig

type ReadTransformerConfig struct {
	// InputBufferLength will dictate the size of the Input buffer.
	InputBufferLength int

	// OutputBufferLength will dictate the size of the Output buffer.
	OutputBufferLength int

	// OutputSampleFormat will define the sample format of the output Reader.
	// This won't always be the same format as the input stream (such as
	// in the case of Conversion)
	OutputSampleFormat sdr.SampleFormat

	// OutputSampleRate will define the sample rate of the output Reader.
	// This won't always be the same rate as the input stream (such as
	// in the case of Decimation).
	OutputSampleRate uint

	// Proc is the actual code to process each chunk of IQ data.
	//
	// This must return the number of samples written to the output buffer.
	// Any errors will be set on the underlying Reader, so that future calls to
	// the Reader returned by the initial ReadTransformer call will return that
	// error.
	Proc func(in sdr.Samples, out sdr.Samples) (int, error)
}

ReadTransformerConfig defines how the ReadTransformer will process samples, and what types of buffers to create.

This is intended to take care of fairly generic cases where a bit of code transforms the *read* path of the IQ data.

type RingBuffer

type RingBuffer struct {
	// contains filtered or unexported fields
}

RingBuffer is an IQ Ring Buffer, where no allocations have to happen to write, designed to take high frequency input data without dealing with things like channel latency or goroutine scheduling.

func NewRingBuffer

func NewRingBuffer(
	rate uint,
	format sdr.SampleFormat,
	opts RingBufferOptions,
) (*RingBuffer, error)

NewRingBuffer will create a RingBuffer with the provided options

func (*RingBuffer) Close

func (rb *RingBuffer) Close() error

Close implements the sdr.Closer interface.

func (*RingBuffer) CloseWithError

func (rb *RingBuffer) CloseWithError(err error) error

CloseWithError will set the error state on the Ring Buffer.

func (*RingBuffer) Read

func (rb *RingBuffer) Read(buf sdr.Samples) (int, error)

Read implements the sdr.Reader interface.

func (*RingBuffer) SampleFormat

func (rb *RingBuffer) SampleFormat() sdr.SampleFormat

SampleFormat implements the sdr.ReadWriteCloser interface.

func (*RingBuffer) SampleRate

func (rb *RingBuffer) SampleRate() uint

SampleRate implements the sdr.ReadWriteCloser interface.

func (*RingBuffer) Write

func (rb *RingBuffer) Write(buf sdr.Samples) (int, error)

Write implements the sdr.Writer interface.

type RingBufferOptions

type RingBufferOptions struct {
	// Slots are the nuber of IQ slots in the Ring Buffer.
	Slots int

	// SlotLength is the max number of IQ samples the Ring Buffer can
	// store, per slot.
	SlotLength int

	// BlockReads will force a wait (rather than an ErrRingBufferUnderrun)
	// if the Read cursor has caught up with the Write cursor.
	BlockReads bool

	// IQBufferAllocator will be passed the configured RingBufferOptions,
	// and allocate an sdr.Samples object that is long enough for the
	// Buffer (Slots*SlotLength at minimum). If Nil, this will use
	// the stock Allocator.
	IQBufferAllocator func(sdr.SampleFormat, RingBufferOptions) (sdr.Samples, error)

	// IQBufferSlotSlicer is responsible for returning a slice of the IQ Buffer
	// allocated to a specific slot. If Nil, this will use the stock Slicer.
	IQBufferSlotSlicer func(sdr.Samples, int, RingBufferOptions) sdr.Samples
}

RingBufferOptions contains configurable options for a Ring Buffer.

type UnsafeRingBuffer

type UnsafeRingBuffer struct {
	*RingBuffer
}

UnsafeRingBuffer is a wrapper around a RingBuffer that allows a few specific and very unsafe things for the sake of I/O critical direct access given a-priori knowledge of the IQ Buffer and a deep understanding of the thread safty.

func NewUnsafeRingBuffer

func NewUnsafeRingBuffer(rb *RingBuffer) *UnsafeRingBuffer

NewUnsafeRingBuffer will create a RingBuffer wrapper around the provided RingBuffer, but will enable some very shady helpers to mutate the RingBuffer given a deep understanding of the underlying magic.

func (*UnsafeRingBuffer) UnsafeGetIQBuffer

func (urb *UnsafeRingBuffer) UnsafeGetIQBuffer() sdr.Samples

UnsafeGetIQBuffer will return the underlying ring buffer as created by the IQBufferAllocator.

func (*UnsafeRingBuffer) WritePeek

func (urb *UnsafeRingBuffer) WritePeek() int

WritePeek will return the underlying Slot the Write cursor will write to next, but without advancing the Write cursor. By the time this function returns, the index can be wrong if any other Writes are happening to this Ring Buffer.

func (*UnsafeRingBuffer) WritePeekUnsafePointer

func (urb *UnsafeRingBuffer) WritePeekUnsafePointer() unsafe.Pointer

WritePeekUnsafePointer will return an unsafe.Pointer pointing to the 0th element of the Slot. This method has the same caviats as WritePeek, and more, since it's wildly unsafe.

func (*UnsafeRingBuffer) WritePoke

func (urb *UnsafeRingBuffer) WritePoke(n int)

WritePoke will write the next slot (blindly) assuming that the caller has used WritePeek to figure out what cell we'll be using next, infer the slice based on the layout, written data there, and ensured that no race conditions can cause any other Writes to the RingBuffer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL