Documentation
¶
Overview ¶
Package autotune tracks the per-dongle carrier-frequency error of an SDR source and turns it into a digital tuning correction, ported in concept from trunk-recorder's AutotuneManager (trunk-recorder/autotune.cc).
Every RTL-SDR crystal is a little off, which shifts the received carrier off centre. A demodulator's AFC / carrier-recovery loop measures that residual offset in Hz once it locks (GopherTrunk reads it from internal/radio/p25/phase1/receiver.Receiver.AFCOffsetHz). This package keeps a running average of the last MaxHistory measurements and exposes it as an offset the caller applies digitally — by shifting the channel's down- converter target — so the next recorder/decoder hands its loop a starting frequency that is already close to lock. It never rewrites the dongle's hardware PPM; it only reports a suggested config value the operator can bake in by hand.
A Manager is per physical dongle (keyed by serial in a Registry): a control SDR and a voice SDR have independent crystal errors and independent corrections.
Index ¶
Constants ¶
const ( // MaxHistory is how many recent error measurements feed the running // average. Matches trunk-recorder's MAX_HISTORY. MaxHistory = 20 // PPMThreshold is the correction magnitude (in PPM, relative to the // channel centre frequency) above which the source is flagged as // mis-configured. Matches trunk-recorder's PPM_THRESHOLD. PPMThreshold = 3.5 // SuggestedErrorRounding rounds the suggested config value to the // nearest N Hz. Matches trunk-recorder's SUGGESTED_ERROR_ROUNDING. SuggestedErrorRounding = 10 // WarmupSamples is how many measurements must accumulate before the // running average is trusted enough to apply. Until then Ready() is // false and callers apply no correction, so a single noisy first // measurement can't yank tuning by hundreds of Hz before the average // has had a chance to settle. WarmupSamples = 3 // MaxAbsErrorHz bounds a single measurement's plausible magnitude when // the channel centre is unknown (the voice path passes centre 0). A // real crystal error is a few PPM — single-digit Hz on a disciplined // USRP, low hundreds on a loose RTL crystal at VHF/UHF. A per-call AFC // residual far beyond that is a measurement glitch (an unconverged loop, // a data-dependent bias snapshot at end-of-call), not drift, and folding // it in produces the runaway kHz corrections this guard prevents. ~1.5 kHz // is ≈3.3 PPM at 450 MHz — comfortably above any correction worth making. MaxAbsErrorHz = 1500 )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager accumulates carrier-error measurements for one SDR source and serves the running-average correction. It is safe for concurrent use: the control decoder and the voice composer both call into the same Manager.
func NewManager ¶
NewManager returns a Manager labelled with the dongle serial for logging. A nil logger is tolerated (logging becomes a no-op).
func (*Manager) AddErrorMeasurement ¶
AddErrorMeasurement folds one observation into the running average. observedHz is the loop's freshly-measured residual offset; currentOffsetHz is the autotune offset that was already applied while that residual was observed, so total = observed + applied is the source's true error. centerFreqHz is the channel centre the measurement was taken at, used only for the PPM sanity warning. Port of AutotuneManager::add_error_measurement.
func (*Manager) AverageError ¶
AverageError returns the cached running-average correction in Hz. Callers apply it digitally as targetHz - AverageError(). Port of get_average_error. This is the raw average regardless of warm-up; callers that drive tuning should gate on Ready() so an unsettled early average is not applied.
func (*Manager) Correction ¶ added in v0.5.1
Correction returns the average correction to apply, or 0 until warm-up completes. It is the value tuning call sites should use.
func (*Manager) Ready ¶ added in v0.5.1
Ready reports whether enough measurements have accumulated (>= WarmupSamples) for the running average to be trusted as an applied tuning correction. Before that, callers should apply no correction.
func (*Manager) Reset ¶
func (m *Manager) Reset()
Reset clears the measurement history. Port of AutotuneManager::reset. The cached average is intentionally left in place so an in-flight correction is not yanked to zero mid-call (matches trunk-recorder, which only clears the deque).
func (*Manager) StatusString ¶
StatusString renders the live correction and a suggested config value, given the channel centre frequency and the operator's currently-configured error in Hz (derive from the dongle's configured ppm: round(ppm * centerHz/1e6)). Port of AutotuneManager::get_status_string, adapted to GopherTrunk's ppm config: it also reports the suggested value as a ppm so it can be pasted into the device block.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry hands out per-serial Managers. When disabled, Get returns nil so every call site collapses to the pre-autotune behaviour at zero cost.
func NewRegistry ¶
NewRegistry builds a Registry. When enabled is false, Get always returns nil.