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 ¶
- Variables
- func Add(readers ...sdr.Reader) (sdr.Reader, error)
- func BeamformAngles(frequency rf.Hz, angle float64, distances []float64) []complex64
- func BeamformAngles2D(frequency rf.Hz, angle float64, center [2]float64, antennas [][2]float64) []complex64
- func ConvertReader(in sdr.Reader, to sdr.SampleFormat) (sdr.Reader, error)
- func ConvertWriter(out sdr.Writer, inputFormat sdr.SampleFormat) (sdr.Writer, error)
- func ConvolutionReader(r sdr.Reader, planner fft.Planner, filter []complex64) (sdr.Reader, error)
- func DecimateBuffer(to, from sdr.Samples, factor uint, offset int) (int, error)
- func DecimateReader(in sdr.Reader, factor uint) (sdr.Reader, error)
- func DownsampleBuffer(to, from sdr.Samples, factor uint, offset int) (int, error)
- func DownsampleReader(in sdr.Reader, factor uint) (sdr.Reader, error)
- func Gain(r sdr.Reader, v float32) sdr.Reader
- func Multiply(r sdr.Reader, m complex64) (sdr.Reader, error)
- func Noise(nc NoiseConfig) sdr.Reader
- func NoisyReader(nc NoiseConfig, r sdr.Reader, snr float32) (sdr.Reader, error)
- func ReadTransformer(in sdr.Reader, config ReadTransformerConfig) (sdr.Reader, error)
- func ShiftBuffer(sampleRate uint) func(rf.Hz, sdr.SamplesC64)
- func ShiftReader(r sdr.Reader, shift rf.Hz) (sdr.Reader, error)
- func StandbyReader(rx sdr.Receiver) (sdr.ReadCloser, error)
- func StandbyWriter(tx sdr.Transmitter) (sdr.WriteCloser, error)
- func Throttle(r sdr.Reader) (sdr.Reader, error)
- func ThrottleSecondsPerSecond(r sdr.Reader, d time.Duration) (sdr.Reader, error)
- type Beamform
- type BeamformConfig
- type BufPipe2
- type NoiseConfig
- type ReadTransformerConfig
- type RingBuffer
- type RingBufferOptions
- type UnsafeRingBuffer
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
DownsampleBuffer will take an oversampled input buffer, and write the downsampled output samples to the target buffer.
func DownsampleReader ¶
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 Multiply ¶
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 ¶
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 ¶
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 ¶
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.
Types ¶
type Beamform ¶
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 ¶
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 ¶
NewBufPipe2 will create a new stream.BufPipe, which wraps a normal sdr.Pipe, but writes will not block.
func (*BufPipe2) Close ¶
Close will close the BufPipe, which will remain readable until the end of buffered data.
func (*BufPipe2) CloseWithError ¶
CloseWithError will close the pipe, and return the provided error on any subsequent call.
func (*BufPipe2) SampleFormat ¶
func (p *BufPipe2) SampleFormat() sdr.SampleFormat
SampleFormat implements the sdr.ReadWriter interface.
func (*BufPipe2) SampleRate ¶
SampleRate 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.
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.