115 lines
3.2 KiB
Python
115 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
FR-2.2: Audio Data Validation
|
|
Validates audio metrics against expected ranges
|
|
"""
|
|
|
|
from typing import NamedTuple
|
|
|
|
|
|
class ValidationResult(NamedTuple):
|
|
"""Validation result"""
|
|
|
|
valid: bool
|
|
error: str = ""
|
|
|
|
|
|
class AudioValidator:
|
|
"""
|
|
Validates audio metrics against hardware constraints and realistic ranges.
|
|
"""
|
|
|
|
# Hardware constraints (from FR spec)
|
|
RMS_MIN_DB = -40.0 # Noise floor
|
|
RMS_MAX_DB = 80.0 # Clipping threshold
|
|
|
|
FREQ_MIN_HZ = 100 # Below = unreliable (FFT bin size ~43Hz)
|
|
FREQ_MAX_HZ = 8000 # Nyquist @ 22.05kHz with safety margin
|
|
|
|
# Extended ranges for detection (not storage)
|
|
FREQ_MIN_EXTENDED_HZ = 20
|
|
FREQ_MAX_EXTENDED_HZ = 11000
|
|
|
|
@staticmethod
|
|
def validate_rms(rms_db: float) -> ValidationResult:
|
|
"""
|
|
Validate RMS value.
|
|
|
|
Args:
|
|
rms_db: RMS in dB
|
|
|
|
Returns:
|
|
ValidationResult with valid flag and error message
|
|
"""
|
|
if not isinstance(rms_db, (int, float)):
|
|
return ValidationResult(False, "RMS must be numeric")
|
|
|
|
if rms_db < AudioValidator.RMS_MIN_DB:
|
|
return ValidationResult(
|
|
False, f"RMS {rms_db:.1f}dB below minimum {AudioValidator.RMS_MIN_DB}dB"
|
|
)
|
|
|
|
if rms_db > AudioValidator.RMS_MAX_DB:
|
|
return ValidationResult(
|
|
False,
|
|
f"RMS {rms_db:.1f}dB exceeds maximum {AudioValidator.RMS_MAX_DB}dB",
|
|
)
|
|
|
|
return ValidationResult(True)
|
|
|
|
@staticmethod
|
|
def validate_frequency(freq_hz: int, strict: bool = True) -> ValidationResult:
|
|
"""
|
|
Validate frequency value.
|
|
|
|
Args:
|
|
freq_hz: Frequency in Hz
|
|
strict: If True, use tight range (100-8000Hz), else extended (20-11000Hz)
|
|
|
|
Returns:
|
|
ValidationResult with valid flag and error message
|
|
"""
|
|
if not isinstance(freq_hz, int):
|
|
return ValidationResult(False, "Frequency must be integer")
|
|
|
|
if strict:
|
|
min_hz = AudioValidator.FREQ_MIN_HZ
|
|
max_hz = AudioValidator.FREQ_MAX_HZ
|
|
else:
|
|
min_hz = AudioValidator.FREQ_MIN_EXTENDED_HZ
|
|
max_hz = AudioValidator.FREQ_MAX_EXTENDED_HZ
|
|
|
|
if freq_hz < min_hz:
|
|
return ValidationResult(
|
|
False, f"Frequency {freq_hz}Hz below minimum {min_hz}Hz"
|
|
)
|
|
|
|
if freq_hz > max_hz:
|
|
return ValidationResult(
|
|
False, f"Frequency {freq_hz}Hz exceeds maximum {max_hz}Hz"
|
|
)
|
|
|
|
return ValidationResult(True)
|
|
|
|
@staticmethod
|
|
def validate_packet(rms_db: float, freq_hz: int) -> ValidationResult:
|
|
"""
|
|
Validate complete audio packet.
|
|
|
|
Args:
|
|
rms_db: RMS in dB
|
|
freq_hz: Frequency in Hz
|
|
|
|
Returns:
|
|
ValidationResult with valid flag and error message
|
|
"""
|
|
rms_result = AudioValidator.validate_rms(rms_db)
|
|
if not rms_result.valid:
|
|
return rms_result
|
|
|
|
freq_result = AudioValidator.validate_frequency(freq_hz, strict=True)
|
|
if not freq_result.valid:
|
|
return freq_result
|
|
|
|
return ValidationResult(True)
|