diff --git a/src/ria_toolkit_oss/sdr/thinkrf.py b/src/ria_toolkit_oss/sdr/thinkrf.py index abdb5a5..ccbe9e9 100644 --- a/src/ria_toolkit_oss/sdr/thinkrf.py +++ b/src/ria_toolkit_oss/sdr/thinkrf.py @@ -306,15 +306,31 @@ class ThinkRF(SDR): raise NotImplementedError("ThinkRF radios do not expose a controllable bias-tee") def _derive_decimation(self, target_sample_rate: int | float) -> int: - """Round sample rate to nearest supported decimation.""" + """ + Derive decimation from target sample rate. + Always rounds DOWN decimation (UP sample rate) to meet or exceed user's requested rate. + + Example: 30 MS/s requested → dec 4 (31.25 MS/s), NOT dec 8 (15.625 MS/s) + """ if not target_sample_rate: return 1 requested = float(target_sample_rate) if requested >= self.BASE_SAMPLE_RATE: return 1 - desired = self.BASE_SAMPLE_RATE / requested - # Always round UP to next decimation (lower sample rate) to avoid exceeding user request - best = min((d for d in self.SUPPORTED_DECIMATIONS if d >= desired), default=self.SUPPORTED_DECIMATIONS[-1]) + + desired_decimation = self.BASE_SAMPLE_RATE / requested + + # Round DOWN decimation (UP sample rate) to meet or exceed requested rate + # Find largest decimation that gives sample rate >= requested + valid_decimations = [d for d in self.SUPPORTED_DECIMATIONS if d <= desired_decimation] + + if valid_decimations: + # Use largest valid decimation (gives sample rate >= requested) + best = max(valid_decimations) + else: + # Requested rate too low, use minimum decimation (max sample rate) + best = self.SUPPORTED_DECIMATIONS[0] + return int(best) def enforce_sample_rate(self, requested_sample_rate: int | float, decimation: Optional[int] = None) -> tuple[int, float]: