[feat](trx-rds,trx-frontend-http): reset rds on tune changes
Co-authored-by: Codex <codex@openai.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -294,6 +294,7 @@ impl Candidate {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RdsDecoder {
|
pub struct RdsDecoder {
|
||||||
|
sample_rate_hz: u32,
|
||||||
carrier_phase: f32,
|
carrier_phase: f32,
|
||||||
carrier_inc: f32,
|
carrier_inc: f32,
|
||||||
i_lp: OnePoleLowPass,
|
i_lp: OnePoleLowPass,
|
||||||
@@ -314,6 +315,7 @@ impl RdsDecoder {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
|
sample_rate_hz: sample_rate.max(1),
|
||||||
carrier_phase: 0.0,
|
carrier_phase: 0.0,
|
||||||
carrier_inc: TAU * RDS_SUBCARRIER_HZ / sample_rate_f,
|
carrier_inc: TAU * RDS_SUBCARRIER_HZ / sample_rate_f,
|
||||||
i_lp: OnePoleLowPass::new(sample_rate_f, 3_000.0),
|
i_lp: OnePoleLowPass::new(sample_rate_f, 3_000.0),
|
||||||
@@ -343,6 +345,10 @@ impl RdsDecoder {
|
|||||||
self.best_state.as_ref()
|
self.best_state.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
*self = Self::new(self.sample_rate_hz);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn snapshot(&self) -> Option<RdsData> {
|
pub fn snapshot(&self) -> Option<RdsData> {
|
||||||
self.best_state.clone()
|
self.best_state.clone()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -671,6 +671,7 @@ function resizeHeaderSignalCanvas() {
|
|||||||
_wfResetOffscreen();
|
_wfResetOffscreen();
|
||||||
trimOverviewWaterfallRows();
|
trimOverviewWaterfallRows();
|
||||||
}
|
}
|
||||||
|
positionRdsPsOverlay();
|
||||||
drawHeaderSignalGraph();
|
drawHeaderSignalGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,6 +752,7 @@ function drawHeaderSignalGraph() {
|
|||||||
drawOverviewSignalHistory(ctx, w, h, pal);
|
drawOverviewSignalHistory(ctx, w, h, pal);
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
positionRdsPsOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _wfDrawRows(oct, rows, startRowIdx, endRowIdx, iW, iH, pal) {
|
function _wfDrawRows(oct, rows, startRowIdx, endRowIdx, iW, iH, pal) {
|
||||||
@@ -927,8 +929,32 @@ function refreshFreqDisplay() {
|
|||||||
refreshWavelengthDisplay(lastFreqHz);
|
refreshWavelengthDisplay(lastFreqHz);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function positionRdsPsOverlay() {
|
||||||
|
if (!rdsPsOverlay || !lastSpectrumData || lastFreqHz == null || !overviewCanvas) return;
|
||||||
|
const width = overviewCanvas.clientWidth || overviewCanvas.width || 0;
|
||||||
|
if (width <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const range = spectrumVisibleRange(lastSpectrumData);
|
||||||
|
if (!Number.isFinite(range.visLoHz) || !Number.isFinite(range.visSpanHz) || range.visSpanHz <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rel = (lastFreqHz - range.visLoHz) / range.visSpanHz;
|
||||||
|
const clamped = Math.max(0.06, Math.min(0.94, rel));
|
||||||
|
rdsPsOverlay.style.left = `${clamped * width}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRdsDisplay() {
|
||||||
|
rdsFrameCount = 0;
|
||||||
|
updateRdsPsOverlay(null);
|
||||||
|
}
|
||||||
|
|
||||||
function applyLocalTunedFrequency(hz) {
|
function applyLocalTunedFrequency(hz) {
|
||||||
if (!Number.isFinite(hz)) return;
|
if (!Number.isFinite(hz)) return;
|
||||||
|
const freqChanged = lastFreqHz !== hz;
|
||||||
|
if (freqChanged) {
|
||||||
|
resetRdsDisplay();
|
||||||
|
}
|
||||||
lastFreqHz = hz;
|
lastFreqHz = hz;
|
||||||
refreshWavelengthDisplay(lastFreqHz);
|
refreshWavelengthDisplay(lastFreqHz);
|
||||||
if (!freqDirty) {
|
if (!freqDirty) {
|
||||||
@@ -941,6 +967,7 @@ function applyLocalTunedFrequency(hz) {
|
|||||||
if (lastSpectrumData) {
|
if (lastSpectrumData) {
|
||||||
scheduleSpectrumDraw();
|
scheduleSpectrumDraw();
|
||||||
}
|
}
|
||||||
|
positionRdsPsOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshCenterFreqDisplay() {
|
function refreshCenterFreqDisplay() {
|
||||||
@@ -3150,6 +3177,7 @@ function updateRdsPsOverlay(rds) {
|
|||||||
const ps = rds?.program_service?.trim();
|
const ps = rds?.program_service?.trim();
|
||||||
if (ps) {
|
if (ps) {
|
||||||
rdsPsOverlay.textContent = ps;
|
rdsPsOverlay.textContent = ps;
|
||||||
|
positionRdsPsOverlay();
|
||||||
rdsPsOverlay.style.display = "";
|
rdsPsOverlay.style.display = "";
|
||||||
} else {
|
} else {
|
||||||
rdsPsOverlay.style.display = "none";
|
rdsPsOverlay.style.display = "none";
|
||||||
|
|||||||
@@ -451,18 +451,29 @@ small { color: var(--text-muted); }
|
|||||||
#rds-ps-overlay {
|
#rds-ps-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(var(--header-waterfall-overlap) + clamp(4.2rem, 11vh, 6.25rem) / 2);
|
top: 0.35rem;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, 0);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
font-family: 'DSEG14 Classic', monospace;
|
font-family: 'DSEG14 Classic', monospace;
|
||||||
font-size: 2rem;
|
font-size: clamp(1rem, 2.2vw, 1.45rem);
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.08em;
|
||||||
color: var(--text-heading);
|
color: var(--text-heading);
|
||||||
opacity: 0.88;
|
padding: 0.22rem 0.6rem 0.16rem;
|
||||||
text-shadow: 0 1px 8px color-mix(in srgb, var(--bg) 60%, transparent);
|
border: 1px solid color-mix(in srgb, var(--border-light) 72%, transparent);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: color-mix(in srgb, var(--card-bg) 52%, transparent);
|
||||||
|
backdrop-filter: blur(14px) saturate(135%);
|
||||||
|
-webkit-backdrop-filter: blur(14px) saturate(135%);
|
||||||
|
box-shadow:
|
||||||
|
0 8px 18px color-mix(in srgb, #000000 16%, transparent),
|
||||||
|
inset 0 1px 0 color-mix(in srgb, #ffffff 10%, transparent);
|
||||||
|
text-shadow: 0 1px 10px color-mix(in srgb, var(--bg) 68%, transparent);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
max-width: min(88vw, 22rem);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.overview-toolbar {
|
.overview-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -149,6 +149,10 @@ impl WfmStereoDecoder {
|
|||||||
pub fn rds_data(&self) -> Option<RdsData> {
|
pub fn rds_data(&self) -> Option<RdsData> {
|
||||||
self.rds_decoder.snapshot()
|
self.rds_decoder.snapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_rds(&mut self) {
|
||||||
|
self.rds_decoder.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selects the demodulation algorithm for a channel.
|
/// Selects the demodulation algorithm for a channel.
|
||||||
|
|||||||
@@ -502,6 +502,12 @@ impl ChannelDsp {
|
|||||||
self.wfm_decoder.as_ref().and_then(WfmStereoDecoder::rds_data)
|
self.wfm_decoder.as_ref().and_then(WfmStereoDecoder::rds_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_rds(&mut self) {
|
||||||
|
if let Some(decoder) = &mut self.wfm_decoder {
|
||||||
|
decoder.reset_rds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Process a block of raw IQ samples through the full DSP chain.
|
/// Process a block of raw IQ samples through the full DSP chain.
|
||||||
///
|
///
|
||||||
/// 1. **Batch mixer**: compute the full LO signal for the block at once,
|
/// 1. **Batch mixer**: compute the full LO signal for the block at once,
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ impl RigCat for SoapySdrRig {
|
|||||||
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
|
) -> Pin<Box<dyn std::future::Future<Output = DynResult<()>> + Send + 'a>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
tracing::debug!("SoapySdrRig: set_freq -> {} Hz", freq.hz);
|
tracing::debug!("SoapySdrRig: set_freq -> {} Hz", freq.hz);
|
||||||
|
let freq_changed = self.freq.hz != freq.hz;
|
||||||
self.freq = freq;
|
self.freq = freq;
|
||||||
let half_span_hz = i128::from(self.pipeline.sdr_sample_rate) / 2;
|
let half_span_hz = i128::from(self.pipeline.sdr_sample_rate) / 2;
|
||||||
let current_center_hz = i128::from(self.center_hz);
|
let current_center_hz = i128::from(self.center_hz);
|
||||||
@@ -271,7 +272,11 @@ impl RigCat for SoapySdrRig {
|
|||||||
|
|
||||||
if let Some(dsp_arc) = self.pipeline.channel_dsps.get(self.primary_channel_idx) {
|
if let Some(dsp_arc) = self.pipeline.channel_dsps.get(self.primary_channel_idx) {
|
||||||
let channel_if_hz = (self.freq.hz as i64 - self.center_hz) as f64;
|
let channel_if_hz = (self.freq.hz as i64 - self.center_hz) as f64;
|
||||||
dsp_arc.lock().unwrap().set_channel_if_hz(channel_if_hz);
|
let mut dsp = dsp_arc.lock().unwrap();
|
||||||
|
dsp.set_channel_if_hz(channel_if_hz);
|
||||||
|
if freq_changed {
|
||||||
|
dsp.reset_rds();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user