Skip to content

Commit 4376578

Browse files
happycubeclaude
andcommitted
rfdecode: half-spectrum rfft/irfft for rfhpf and video outputs
Port from dp11's tree: the rfhpf dropout filter and the FVideo/FVideo05/ FVideoBurst/FVideoPilot output cluster operate on real signals through conjugate-symmetric filters, so the full complex fft/ifft.real pairs are exactly replaceable by half-spectrum rfft/irfft at roughly half the transform cost (~2.3x on the transforms themselves). Verified bit-identical (.tbc/.pcm/.efm md5) on ve-snw-cut (NTSC) and jason-testpattern (PAL), both -t8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 9a58a7d commit 4376578

1 file changed

Lines changed: 21 additions & 7 deletions

File tree

lddecode/rfdecode.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,10 @@ def computevideofilters(self):
376376
# This high pass filter is intended to detect RF dropouts
377377
Frfhpf = sps.butter(1, 10 / self.freq_half, btype="highpass")
378378
self.Filters["Frfhpf"] = filtfft(Frfhpf, self.blocklen)
379+
# Frfhpf is a real (conjugate-symmetric) filter and the input RF is real,
380+
# so ifft(indata_fft * Frfhpf).real is exactly irfft over the half spectrum
381+
# at ~half the transform cost. Keep the positive-frequency half.
382+
self.Filters["Frfhpf_half"] = self.Filters["Frfhpf"][: self.blocklen // 2 + 1]
379383

380384
# First phase FFT filtering
381385

@@ -691,7 +695,12 @@ def demodblock(self, data=None, mtf_level=0, fftdata=None, cut=False,
691695
if getattr(self, "delays", None) is not None and "video_rot" in self.delays:
692696
rotdelay = self.delays["video_rot"]
693697

694-
rv["rfhpf"] = npfft.ifft(indata_fft * self.Filters["Frfhpf"]).real
698+
# Real filter + real RF input => half-spectrum irfft is exact (see
699+
# computevideofilters).
700+
nrf = indata_fft.shape[0] // 2 + 1
701+
rv["rfhpf"] = npfft.irfft(
702+
indata_fft[:nrf] * self.Filters["Frfhpf_half"], n=self.blocklen
703+
)
695704
rv["rfhpf"] = rv["rfhpf"][
696705
self.blockcut - rotdelay : -self.blockcut_end - rotdelay
697706
].astype(np.float32)
@@ -730,17 +739,22 @@ def demodblock(self, data=None, mtf_level=0, fftdata=None, cut=False,
730739
hilbert = npfft.ifft(indata_fft_filt)
731740
demod = unwrap_hilbert(hilbert, self.freq_hz)
732741

733-
# use a clipped demod for video output processing to reduce speckling impact
734-
demod_fft = npfft.fft(np.clip(demod, 1500000, self.freq_hz * 0.75))
742+
# use a clipped demod for video output processing to reduce speckling impact.
743+
# demod is real and these video outputs are real, so the half-spectrum
744+
# rfft/irfft pair is mathematically identical to fft/ifft.real (the filters
745+
# are conjugate-symmetric) at ~2.3x the speed of the full complex transforms.
746+
demod_fft = npfft.rfft(np.clip(demod, 1500000, self.freq_hz * 0.75))
747+
nr = demod_fft.shape[0]
748+
bl = self.blocklen
735749

736-
out_video = npfft.ifft(demod_fft * self.Filters["FVideo"]).real
750+
out_video = npfft.irfft(demod_fft * self.Filters["FVideo"][:nr], n=bl)
737751

738-
out_video05 = npfft.ifft(demod_fft * self.Filters["FVideo05"]).real
752+
out_video05 = npfft.irfft(demod_fft * self.Filters["FVideo05"][:nr], n=bl)
739753

740-
out_videoburst = npfft.ifft(demod_fft * self.Filters["FVideoBurst"]).real
754+
out_videoburst = npfft.irfft(demod_fft * self.Filters["FVideoBurst"][:nr], n=bl)
741755

742756
if self.system == "PAL":
743-
out_videopilot = npfft.ifft(demod_fft * self.Filters["FVideoPilot"]).real
757+
out_videopilot = npfft.irfft(demod_fft * self.Filters["FVideoPilot"][:nr], n=bl)
744758
video_out = np.rec.array(
745759
[
746760
out_video.astype(np.float32),

0 commit comments

Comments
 (0)