### DeemphasisFilter Usage Example Source: https://docs.rs/fmradio/latest/fmradio/fm/struct.DeemphasisFilter.html Demonstrates how to create and use the DeemphasisFilter for processing audio samples. This example uses the European standard time constant. ```rust use fmradio::fm::DeemphasisFilter; // European FM broadcast (50 μs time constant) let mut filter = DeemphasisFilter::new(240_000.0, 50e-6); let input = vec![0.1, 0.2, 0.3, 0.4, 0.5]; let output = filter.process(&input); assert_eq!(output.len(), 5); ``` -------------------------------- ### PhaseExtractor Usage Source: https://docs.rs/fmradio/latest/src/fmradio/fm.rs.html Shows how to initialize and use the PhaseExtractor to get phase information from complex samples. ```rust use fmradio::fm::PhaseExtractor; use num_complex::Complex; let mut extractor = PhaseExtractor::new(); let samples = vec![ Complex::new(1.0, 0.0), Complex::new(0.707, 0.707), Complex::new(0.0, 1.0), ]; let phase = extractor.process(&samples); assert_eq!(phase.len(), 3); ``` -------------------------------- ### FM Demodulation Example Source: https://docs.rs/fmradio/latest/fmradio/fm/index.html Demonstrates how to use PhaseExtractor and DeemphasisFilter to demodulate an FM signal. Requires initializing both components and processing IQ samples. ```rust use fmradio::fm::{PhaseExtractor, DeemphasisFilter}; use num_complex::Complex; let mut phase_extractor = PhaseExtractor::new(); let mut deemphasis = DeemphasisFilter::new(240_000.0, 50e-6); // Demodulate FM signal let iq_samples = vec![Complex::new(0.5, 0.5); 100]; let phase = phase_extractor.process(&iq_samples); let audio = deemphasis.process(&phase); ``` -------------------------------- ### Example Usage of PhaseExtractor Source: https://docs.rs/fmradio/latest/fmradio/fm/struct.PhaseExtractor.html Demonstrates how to create a PhaseExtractor, process complex samples, and assert the output length. Requires importing Complex from num_complex. ```rust use fmradio::fm::PhaseExtractor; use num_complex::Complex; let mut extractor = PhaseExtractor::new(); let samples = vec![ Complex::new(1.0, 0.0), Complex::new(0.707, 0.707), Complex::new(0.0, 1.0), ]; let phase = extractor.process(&samples); assert_eq!(phase.len(), 3); ``` -------------------------------- ### Get Language Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Retrieves the decoded language of the program, if available. Returns an Option. ```rust pub fn language(&self) -> Option ``` -------------------------------- ### Get Program Identification (PI) Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Returns the 16-bit Program Identification (PI) code for the current FM station. ```rust pub fn program_id(&self) -> u16 { self.station_info.pi } ``` -------------------------------- ### StereoDecoderPLL pilot_phase() method Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.StereoDecoderPLL.html Retrieves the current PLL phase for the 19 kHz pilot tone. Multiply by 3 to get the phase for the 57 kHz RDS carrier. ```rust pub fn pilot_phase(&self) -> f64 ``` -------------------------------- ### Get Station Name Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Retrieves the decoded station name (PS - Program Service) as an Option. This is the text name of the radio station. ```rust pub fn station_name(&self) -> Option ``` -------------------------------- ### Get Current Station Name (PS) Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Retrieves the last complete Program Service (PS) name, which is 8 characters long. Returns None if not yet fully received. ```rust pub fn station_name(&self) -> Option { // Return the cached complete string if we have one self.ps_last_complete.clone() } ``` -------------------------------- ### Get Open Data Application (ODA) Name by ID Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Looks up and returns the static string name for an Open Data Application (ODA) given its application ID. Returns 'Unknown' if the ID is not found. ```rust pub fn oda_app_name(app_id: u16) -> &'static str { for &(id, name) in ODA_APPS { if id == app_id { return name; } } "Unknown" } ``` -------------------------------- ### Initialize and Use AdaptiveResampler Source: https://docs.rs/fmradio/latest/fmradio/resampler/index.html Demonstrates how to initialize an AdaptiveResampler with a specific ratio and channel count, and then process audio data. ```rust use fmradio::resampler::AdaptiveResampler; // Resample from 240kHz to 48kHz (mono) let mut resampler = AdaptiveResampler::new( 48_000.0 / 240_000.0, // Initial ratio 1, // Adjustment interval 1, // Mono (1 channel) ).unwrap(); let input = vec![0.0; 1000]; let output = resampler.process(&input); ``` -------------------------------- ### Get Program Type Name Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Retrieves the name of the current program type (PTY) decoded from the RDS signal. For example, 'Rock', 'News', 'Pop'. ```rust pub fn program_type(&self) -> String { self.rds_parser.program_type_name().to_string() } ``` -------------------------------- ### Create and Use AdaptiveResampler Source: https://docs.rs/fmradio/latest/fmradio/resampler/struct.AdaptiveResampler.html Demonstrates creating a mono AdaptiveResampler and processing audio samples. It also shows how to manually adjust the resampling ratio based on buffer fill level. ```rust use fmradio::resampler::AdaptiveResampler; // Create mono resampler from FM bandwidth to audio rate let mut resampler = AdaptiveResampler::new( 48_000.0 / 240_000.0, // ratio 1, // adjustment interval 1, // channels ).unwrap(); // Process audio let input = vec![0.5; 240]; let output = resampler.process(&input); // Adjust based on buffer fill (0.0 = empty, 1.0 = full) resampler.adjust_ratio(0.3); // 30% full ``` -------------------------------- ### Create AdaptiveResampler Instances Source: https://docs.rs/fmradio/latest/fmradio/resampler/struct.AdaptiveResampler.html Shows how to instantiate AdaptiveResampler for mono and stereo audio with default PI controller parameters. ```rust use fmradio::resampler::AdaptiveResampler; // Mono resampler let mono = AdaptiveResampler::new(0.2, 1, 1).unwrap(); // Stereo resampler let stereo = AdaptiveResampler::new(0.2, 5, 2).unwrap(); ``` -------------------------------- ### RdsParser::program_type_name Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Gets the human-readable name of the current program type (PTY). ```APIDOC ## RdsParser::program_type_name ### Description Returns the string name corresponding to the current Program Type (PTY) code. PTY codes indicate the type of content being broadcast (e.g., News, Rock, Pop). ### Method `program_type_name(&self) -> &str` ### Returns A static string slice representing the program type name. ``` -------------------------------- ### type_id() method implementation Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.StereoDecoderPLL.html Gets the `TypeId` of `self`. This is part of the `Any` trait implementation. ```rust fn type_id(&self) -> TypeId ``` -------------------------------- ### Create AdaptiveResampler with Custom PI Parameters Source: https://docs.rs/fmradio/latest/fmradio/resampler/struct.AdaptiveResampler.html Demonstrates creating an AdaptiveResampler with custom PI controller parameters for more aggressive adaptation, including target buffer fill and gain values. ```rust use fmradio::resampler::AdaptiveResampler; // More aggressive adaptation let resampler = AdaptiveResampler::with_params( 0.2, 5, 2, // ratio, interval, channels 0.4, // target 40% buffer fill 0.002, // higher proportional gain 5e-6, // higher integral gain ).unwrap(); ``` -------------------------------- ### RdsParser::language_name Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Gets the human-readable name for the language code specified in Group 1A. ```APIDOC ## RdsParser::language_name ### Description Translates the language code found in Group 1A RDS transmissions into its corresponding human-readable language name (e.g., 'English', 'French'). Returns `None` if the code is invalid or not found. ### Method `language_name(&self) -> Option<&'static str>` ### Returns An `Option<&'static str>` containing the language name if available and valid, otherwise `None`. ``` -------------------------------- ### Creating Mono and Stereo Adaptive Resamplers Source: https://docs.rs/fmradio/latest/src/fmradio/resampler.rs.html Demonstrates how to create both mono and stereo adaptive resamplers with specified initial ratios and adjustment intervals. This shows the flexibility in configuring the resampler for different audio channel counts. ```rust use fmradio::resampler::AdaptiveResampler; // Mono resampler let mono = AdaptiveResampler::new(0.2, 1, 1).unwrap(); // Stereo resampler let stereo = AdaptiveResampler::new(0.2, 5, 2).unwrap(); ``` -------------------------------- ### StereoDecoderPLL Initialization Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Initializes the StereoDecoderPLL with a given sample rate. Sets up internal PLL parameters and low-pass filters for pilot tone extraction and stereo separation. ```rust pub fn new(sample_rate: f32) -> Self { let nominal = 19_000.0_f64; let fc = 50.0; let error_lpf_alpha = 2.0 * std::f64::consts::PI * fc / (sample_rate as f64 + 2.0 * std::f64::consts::PI * fc); Self { sample_rate, pll_phase: 0.0, pll_freq: nominal, nominal_freq: nominal, kp: 0.15, ki: 0.0005, error_lpf_state: 0.0, error_lpf_alpha, pilot_hi: StatefulLowPassFir::new(19_700.0, sample_rate, 257), pilot_lo: StatefulLowPassFir::new(18_300.0, sample_rate, 257), diff_lowpass: StatefulLowPassFir::new(15_000.0, sample_rate, 257), } } ``` -------------------------------- ### Get Program Type Name (PTY) Source: https://docs.rs/fmradio/latest/fmradio/rds/parser/struct.RdsParser.html Retrieves the program type name (PTY) as a string slice. ```rust pub fn program_type_name(&self) -> &str ``` -------------------------------- ### AdaptiveResampler Creation with Parameters Source: https://docs.rs/fmradio/latest/src/fmradio/resampler.rs.html Creates an AdaptiveResampler with advanced parameters including target fill, proportional gain (k_p), and integral gain (k_i). Verifies the parameter values after creation. ```rust let resampler = AdaptiveResampler::with_params(0.2, 5, 1, 0.4, 0.002, 5e-6); assert!(resampler.is_ok()); let resampler = resampler.unwrap(); assert_eq!(resampler.target_fill, 0.4); assert_eq!(resampler.k_p, 0.002); assert_eq!(resampler.k_i, 5e-6); ``` -------------------------------- ### StereoDecoderPLL new() method Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.StereoDecoderPLL.html Constructor for StereoDecoderPLL. Initializes the stereo decoder with a given sample rate. ```rust pub fn new(sample_rate: f32) -> Self ``` -------------------------------- ### Get Program Type Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Retrieves the decoded Program Type (PTY) as a String. This indicates the genre of the radio program. ```rust pub fn program_type(&self) -> String ``` -------------------------------- ### Creating DeemphasisFilter instances Source: https://docs.rs/fmradio/latest/fmradio/fm/struct.DeemphasisFilter.html Shows how to instantiate a DeemphasisFilter with different time constants for North American and European FM broadcast standards. ```rust use fmradio::fm::DeemphasisFilter; // North American FM broadcast let filter_na = DeemphasisFilter::new(240_000.0, 75e-6); // European FM broadcast let filter_eu = DeemphasisFilter::new(240_000.0, 50e-6); ``` -------------------------------- ### Get DI Flag by Segment Source: https://docs.rs/fmradio/latest/fmradio/rds/parser/struct.DIFlags.html Retrieves the current boolean value of a DI flag using its segment address. ```rust pub fn get_flag(&self, segment: usize) -> bool ``` -------------------------------- ### Get ODA Application Name Source: https://docs.rs/fmradio/latest/fmradio/rds/parser/struct.RdsParser.html Retrieves the Open Data Application (ODA) name corresponding to a given application ID. ```rust pub fn oda_app_name(app_id: u16) -> &'static str ``` -------------------------------- ### DeemphasisFilter Initialization Source: https://docs.rs/fmradio/latest/src/fmradio/fm.rs.html Illustrates how to create a DeemphasisFilter, specifying the sample rate and time constant for European FM broadcast. ```rust use fmradio::fm::DeemphasisFilter; // European FM broadcast (50 μs time constant) let mut filter = DeemphasisFilter::new(240_000.0, 50e-6); let input = vec![0.1, 0.2, 0.3, 0.4, 0.5]; let output = filter.process(&input); assert_eq!(output.len(), 5); ``` -------------------------------- ### Get Language Name Source: https://docs.rs/fmradio/latest/fmradio/rds/parser/struct.RdsParser.html Retrieves the language name for a Group 1A language code. Returns None if the code is not recognized. ```rust pub fn language_name(&self) -> Option<&'static str> ``` -------------------------------- ### Get Radio Text Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Retrieves the decoded Radio Text (RT) message as an Option. This can be scrolling text from the station. ```rust pub fn radio_text(&self) -> Option ``` -------------------------------- ### Initialize AdaptiveResampler with Custom Parameters Source: https://docs.rs/fmradio/latest/src/fmradio/resampler.rs.html Creates an AdaptiveResampler instance with specific PI controller gains for more aggressive adaptation. Use this when fine-tuning the adaptation behavior is critical. ```rust use fmradio::resampler::AdaptiveResampler; // More aggressive adaptation let resampler = AdaptiveResampler::with_params( 0.2, 5, 2, // ratio, interval, channels 0.4, // target 40% buffer fill 0.002, // higher proportional gain 5e-6, // higher integral gain ).unwrap(); ``` -------------------------------- ### Get Program ID Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Retrieves the decoded Program Identification (PI) code as a u16. This uniquely identifies the radio program. ```rust pub fn program_id(&self) -> u16 ``` -------------------------------- ### RdsParser::new Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Constructs a new RdsParser instance with default settings. ```APIDOC ## RdsParser::new ### Description Creates a new `RdsParser` with default initial values for all its fields, ready to process RDS data. ### Method `RdsParser::new()` ### Returns A new `RdsParser` instance. ``` -------------------------------- ### Initialize RdsDecoder Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Creates a new RdsDecoder instance. It expects an input sample rate of 171 kHz, matching the redsea architecture, and an option to enable verbose output. ```rust pub fn new(_sample_rate: f32, verbose: bool) -> Self ``` -------------------------------- ### Get Clock Time Information Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Retrieves decoded Clock Time Information (CT) if available. Returns an Option. ```rust pub fn clock_time(&self) -> Option ``` -------------------------------- ### Get DI Flag Name by Segment Source: https://docs.rs/fmradio/latest/fmradio/rds/parser/struct.DIFlags.html Retrieves the descriptive name of a DI flag based on its segment address (0-3). ```rust pub fn flag_name(segment: usize) -> &'static str ``` -------------------------------- ### AdaptiveResampler::new Source: https://docs.rs/fmradio/latest/src/fmradio/resampler.rs.html Creates a new AdaptiveResampler instance with specified initial resampling ratio, adjustment interval, and number of channels. This is the primary constructor for setting up the resampler for audio processing. ```APIDOC ## AdaptiveResampler::new ### Description Create a new adaptive resampler. ### Arguments * `initial_ratio` (f64) - Initial resampling ratio (output_rate / input_rate) * `adjustment_interval` (usize) - How often to adjust the ratio (in process() calls) * `channels` (usize) - Number of audio channels (1 = mono, 2 = stereo, etc.) ### Returns Returns `Ok(AdaptiveResampler)` on success, or `Err(String)` if resampler creation fails. ### Example ```no_run use fmradio::resampler::AdaptiveResampler; // Mono resampler let mono = AdaptiveResampler::new(0.2, 1, 1).unwrap(); // Stereo resampler let stereo = AdaptiveResampler::new(0.2, 5, 2).unwrap(); ``` ``` -------------------------------- ### Get RDS Statistics Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Returns decoding statistics for the RDS parser, including counts of received groups, errors, and other relevant metrics. ```rust pub fn stats(&self) -> (u64, u32, u32, u32) { self.rds_parser.stats() } ``` -------------------------------- ### Initialize DeemphasisFilter and Check Coefficients Source: https://docs.rs/fmradio/latest/src/fmradio/fm.rs.html Initializes a DeemphasisFilter with a specific sample rate and time constant, then verifies that the filter coefficients `a` and `b` sum to approximately 1.0, indicating a DC gain of 1. ```rust let filter = DeemphasisFilter::new(240_000.0, 50e-6); // Coefficients should sum to approximately 1 for DC gain of 1 assert_relative_eq!(filter.a + filter.b, 1.0, epsilon = 1e-6); assert_eq!(filter.prev_y, 0.0); ``` -------------------------------- ### Get Program ID Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Returns the Program ID (PI) code decoded from the RDS signal. This is a unique identifier for the broadcast program. ```rust pub fn program_id(&self) -> u16 { self.rds_parser.program_id() } ``` -------------------------------- ### Get SLC Variant Name Source: https://docs.rs/fmradio/latest/fmradio/rds/parser/struct.RdsParser.html Converts a Slow Labeling Code (SLC) variant byte into its corresponding static string name. ```rust pub fn slc_variant_name(variant: u8) -> &'static str ``` -------------------------------- ### AdaptiveResampler::new Source: https://docs.rs/fmradio/latest/fmradio/resampler/struct.AdaptiveResampler.html Creates a new AdaptiveResampler with default PI controller parameters. It takes an initial resampling ratio, an adjustment interval, and the number of audio channels. ```APIDOC ## AdaptiveResampler::new ### Description Creates a new adaptive resampler. ### Arguments * `initial_ratio` (f64) - Initial resampling ratio (output_rate / input_rate) * `adjustment_interval` (usize) - How often to adjust the ratio (in process() calls) * `channels` (usize) - Number of audio channels (1 = mono, 2 = stereo, etc.) ### Returns Result - Returns `Ok(AdaptiveResampler)` on success, or `Err(String)` if resampler creation fails. ### Example ```rust use fmradio::resampler::AdaptiveResampler; // Mono resampler let mono = AdaptiveResampler::new(0.2, 1, 1).unwrap(); // Stereo resampler let stereo = AdaptiveResampler::new(0.2, 5, 2).unwrap(); ``` ``` -------------------------------- ### StereoDecoderPLL process() method Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.StereoDecoderPLL.html Processes audio input using the stereo decoder. Returns processed audio, and pilot/RDS data. ```rust pub fn process(&mut self, input: &[f32]) -> (Vec, Vec, Vec) ``` -------------------------------- ### RdsResamplerCustom::new Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsResamplerCustom.html Constructs a new RdsResamplerCustom instance. It takes the input and output sample rates as arguments. ```APIDOC ## RdsResamplerCustom::new ### Description Constructs a new RdsResamplerCustom instance with the specified input and output rates. ### Signature ```rust pub fn new(input_rate: f32, output_rate: f32) -> Self ``` ### Parameters * **input_rate** (f32) - The input sample rate. * **output_rate** (f32) - The output sample rate. ### Returns A new `RdsResamplerCustom` instance. ``` -------------------------------- ### Initialize RdsDecoder Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Creates a new RdsDecoder instance with specific DSP parameters matching the redsea architecture. It configures the NCO, filters, AGC, and symbol synchronizer based on fixed sample rates and symbol rates. ```rust pub fn new(_sample_rate: f32, verbose: bool) -> Self { let mut rds_parser = RdsParser::new(); rds_parser.set_verbose(verbose); rds_parser.set_json_mode(true); // Fixed parameters matching redsea exactly let sample_rate = 171_000.0_f32; // We now expect 171 kHz input (from resampler) let decimate_ratio = 24_usize; // Fixed: 171000 / 24 = 7125 Hz let decimated_rate = 7125.0_f32; // Exactly 3 samples per 2375 Hz symbol let psk_symbol_rate = 2375.0_f32; debug!( "[RDS-DSP] Sample rate: {} Hz, Decimate ratio: {}, Decimated rate: {} Hz", sample_rate, decimate_ratio, decimated_rate ); debug!( "[RDS-DSP] Samples per PSK symbol: {:.2}, PSK rate: {:.1} Hz", decimated_rate / psk_symbol_rate, psk_symbol_rate ); // Lowpass at 2400 Hz (matching redsea) let lpf_cutoff = 2400.0; // Use 255 taps for good selectivity let lpf_taps = 255; // NCO for fine phase tracking (IQ path: 57 kHz already removed by pilot-coherent mixing) // In IQ mode, we only need to track residual phase, so frequency is 0 // The NCO runs at the DECIMATED rate (7125 Hz) for phase rotation at sample level let decimated_rate = 7125.0_f64; let mut nco = Nco::new(0.0, decimated_rate); // Zero frequency for IQ path // PLL bandwidth: coefficients must match the PLL UPDATE rate (symbol rate ~2375 Hz), // since pll_step() is called once per symbol, not once per decimated sample. let symbol_rate = 2375.0_f64; nco.set_pll_bandwidth(2.0, symbol_rate); // 2 Hz acquisition bandwidth at symbol rate debug!( "[RDS-DSP] NCO: freq=0 Hz (IQ path), PLL bandwidth=2Hz at symbol rate {}", symbol_rate ); // AGC with bandwidth matching redsea (~500 Hz at 171 kHz = 0.003) // redsea: agc.init(kAGCBandwidth_Hz / kTargetSampleRate_Hz, kAGCInitialGain) // kAGCBandwidth_Hz = 500.0, so 500/171000 ≈ 0.003 // kAGCInitialGain = 0.08 let mut agc = Agc::new(0.003); agc.set_gain(0.08); // Match redsea initial gain debug!("[RDS-DSP] AGC: bandwidth=0.003, initial_gain=0.08"); // Polyphase symbol synchronizer (matching redsea's liquid-dsp symsync) // redsea: symsync.init(LIQUID_FIRFILT_RRC, kSamplesPerSymbol, kSymsyncDelay, kSymsyncBeta, 32) // symsync.setBandwidth(kSymsyncBandwidth_Hz / kTargetSampleRate_Hz) // kSymsyncBandwidth_Hz = 2200.0, so 2200/171000 ≈ 0.013 let symsync = SymSync::new_rnyquist(3, 32, 3, 0.8, 0.013); debug!("[RDS-DSP] SymSync: k=3, npfb=32, m=3, beta=0.8, bandwidth=0.013"); Self { nco, lpf_i: StatefulFir::new(lpf_cutoff, sample_rate, lpf_taps), lpf_q: StatefulFir::new(lpf_cutoff, sample_rate, lpf_taps), agc, symsync, decimate_ratio, decimate_counter: 0, prev_psk_symbol: (0.0, 0.0), biphase_clock: 0, biphase_polarity: 0, biphase_history: vec![0.0; 128], prev_decoded_bit: false, bits: Vec::new(), rds_parser, debug_counter: 0, pll_locked: false, pll_lock_counter: 0, print_json_output: true, } } ``` -------------------------------- ### Get Station Metadata Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Provides read-only access to the StationInfo struct, which contains metadata like PI, PTY, DI, and AF list. ```rust pub fn station_info(&self) -> &StationInfo { &self.station_info } ``` -------------------------------- ### Get RdsParser Statistics Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Retrieves the current statistics of the RdsParser, including bits pushed, blocks received, groups decoded, and blocks checked. ```rust pub fn stats(&self) -> (u64, u32, u32, u32) { ( self.bits_pushed, self.blocks_received, self.groups_decoded, self.blocks_checked, ) } ``` -------------------------------- ### RdsDecoder::new Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Creates a new RdsDecoder instance. It expects an input sample rate of 171 kHz, matching the redsea architecture, and can be configured for verbose output. ```APIDOC ## RdsDecoder::new ### Description Creates a new RdsDecoder instance. It expects an input sample rate of 171 kHz, matching the redsea architecture, and can be configured for verbose output. ### Signature `pub fn new(_sample_rate: f32, verbose: bool) -> Self` ### Parameters * `_sample_rate` (f32) - The input sample rate (expected to be 171 kHz). * `verbose` (bool) - If true, enables verbose output. ``` -------------------------------- ### Initialize New RdsParser Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Creates and returns a new RdsParser instance with default values for all its fields. ```rust pub fn new() -> Self { Self { shift: 0, shift_len: 0, ps: [b' '; 8], ps_received_mask: 0, ps_sequential_len: 0, ps_prev_pos: None, ps_last_complete: None, rt: vec![b' '; 64], rt_received_mask: vec![false; 16], // 16 segments of 4 chars for RT 2A verbose: false, station_info: StationInfo::default(), groups_decoded: 0, blocks_received: 0, blocks_checked: 0, bits_pushed: 0, is_synced: false, expected_offset: 'A', bits_until_next_block: 1, // Check immediately on first bit current_group: [(false, 0); 4], block_error_sum: RunningSum::new(), use_fec: true, // Enable FEC by default (like redsea) sync_buffer: SyncPulseBuffer::new(), bitcount: 0, json_output_queue: Vec::new(), json_mode: false, } } ``` -------------------------------- ### Get Station Metadata Source: https://docs.rs/fmradio/latest/fmradio/rds/parser/struct.RdsParser.html Retrieves a reference to the StationInfo struct, which contains metadata like TP, TA, PTY, DI, and AF list. ```rust pub fn station_info(&self) -> &StationInfo ``` -------------------------------- ### AdaptiveResampler::new Source: https://docs.rs/fmradio/latest/src/fmradio/resampler.rs.html Creates a new AdaptiveResampler instance with default PI controller parameters. It takes the nominal resampling ratio, and the number of channels for input and output. ```APIDOC ## AdaptiveResampler::new ### Description Creates a new AdaptiveResampler instance with default PI controller parameters. It takes the nominal resampling ratio, and the number of channels for input and output. ### Method `AdaptiveResampler::new(nominal_ratio: f64, in_channels: usize, out_channels: usize) -> Result` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```rust let resampler = AdaptiveResampler::new(0.2, 1, 1); ``` ### Response #### Success Response - `Ok(AdaptiveResampler)`: A new instance of AdaptiveResampler. - `Err(&'static str)`: An error message if creation fails (e.g., invalid channel counts). #### Response Example ```rust // On success let resampler = AdaptiveResampler::new(0.2, 1, 1).unwrap(); // On failure // let error = AdaptiveResampler::new(0.2, 0, 1).unwrap_err(); ``` ``` -------------------------------- ### DeemphasisFilter::reset Source: https://docs.rs/fmradio/latest/fmradio/fm/struct.DeemphasisFilter.html Resets the internal state of the de-emphasis filter. This is useful for clearing the previous output sample when starting a new signal stream. ```APIDOC ## DeemphasisFilter::reset ### Description Reset the internal state. Clears the previous output sample, useful when starting a new signal stream. ### Example ```rust use fmradio::fm::DeemphasisFilter; let mut filter = DeemphasisFilter::new(240_000.0, 50e-6); // ... process some samples ... filter.reset(); // Reset the filter state ``` ``` -------------------------------- ### Adjust Resampling Ratio with AdaptiveResampler Source: https://docs.rs/fmradio/latest/fmradio/resampler/struct.AdaptiveResampler.html Demonstrates how to initialize and use the AdaptiveResampler to dynamically adjust the resampling ratio based on buffer fill. This method should be called periodically within a streaming loop. ```rust use fmradio::resampler::AdaptiveResampler; let mut resampler = AdaptiveResampler::new(0.2, 5, 1).unwrap(); // In streaming loop: let output = resampler.process(&input_data); let fill_ratio = buffer.len() as f64 / buffer.capacity() as f64; resampler.adjust_ratio(fill_ratio); ``` -------------------------------- ### Process Audio Samples with AdaptiveResampler Source: https://docs.rs/fmradio/latest/fmradio/resampler/struct.AdaptiveResampler.html Illustrates processing a slice of input audio samples through an AdaptiveResampler and obtaining the resampled output. ```rust use fmradio::resampler::AdaptiveResampler; let mut resampler = AdaptiveResampler::new(0.2, 1, 1).unwrap(); let input = vec![0.1, 0.2, 0.3, 0.4, 0.5]; let output = resampler.process(&input); ``` -------------------------------- ### Get Program Type Name (PTY) Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Retrieves the human-readable name for the current program type (PTY) based on the station's PTY code. ```rust pub fn program_type_name(&self) -> &str { PTY_NAMES[self.station_info.program_type as usize] } ``` -------------------------------- ### Get Language Name Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Returns the language name associated with the current broadcast, as decoded from the RDS signal. Returns None if no language information is available. ```rust pub fn language(&self) -> Option { self.rds_parser.language_name().map(|s| s.to_string()) } ``` -------------------------------- ### AdaptiveResampler with Buffer Adjustment Source: https://docs.rs/fmradio/latest/src/fmradio/resampler.rs.html Creates a mono resampler and processes audio, then adjusts the resampling ratio based on a target buffer fill level. This highlights the adaptive nature of the resampler for maintaining buffer stability. ```rust use fmradio::resampler::AdaptiveResampler; // Create mono resampler from FM bandwidth to audio rate let mut resampler = AdaptiveResampler::new( 48_000.0 / 240_000.0, // ratio 1, // adjustment interval 1, // channels ).unwrap(); // Process audio let input = vec![0.5; 240]; let output = resampler.process(&input); // Adjust based on buffer fill (0.0 = empty, 1.0 = full) resampler.adjust_ratio(0.3); // 30% full ``` -------------------------------- ### PhaseExtractor::new Source: https://docs.rs/fmradio/latest/fmradio/fm/struct.PhaseExtractor.html Creates a new instance of the PhaseExtractor. It initializes the internal state with a unit complex number as the reference. ```APIDOC ## PhaseExtractor::new ### Description Creates a new phase extractor, initializing with a unit complex number (1+0j) as the starting reference. ### Method `new()` ### Returns A new `PhaseExtractor` instance. ``` -------------------------------- ### Get Clock Time Info Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Retrieves the current clock time information decoded from the RDS signal. Returns an Option containing ClockTimeInfo if available. ```rust pub fn clock_time(&self) -> Option { self.rds_parser.station_info().clock_time.clone() } ``` -------------------------------- ### AdaptiveResampler::with_params Source: https://docs.rs/fmradio/latest/fmradio/resampler/struct.AdaptiveResampler.html Creates a new AdaptiveResampler with custom PI controller parameters, allowing fine-tuning of the buffer fill target and controller gains. ```APIDOC ## AdaptiveResampler::with_params ### Description Create a new adaptive resampler with custom PI controller parameters. ### Arguments * `initial_ratio` (f64) - Initial resampling ratio * `adjustment_interval` (usize) - Adjustment frequency * `channels` (usize) - Number of channels * `target_fill` (f64) - Target buffer fill level (0.0-1.0) * `k_p` (f64) - Proportional gain for PI controller * `k_i` (f64) - Integral gain for PI controller ### Returns Result - Returns `Ok(AdaptiveResampler)` on success, or `Err(String)` if resampler creation fails. ### Example ```rust use fmradio::resampler::AdaptiveResampler; // More aggressive adaptation let resampler = AdaptiveResampler::with_params( 0.2, 5, 2, // ratio, interval, channels 0.4, // target 40% buffer fill 0.002, // higher proportional gain 5e-6, // higher integral gain ).unwrap(); ``` ``` -------------------------------- ### Get All Alternative Frequencies Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Returns a vector containing all decoded alternative frequencies in Hz. This provides a comprehensive list of related frequencies broadcast by the station. ```rust pub fn alt_frequencies(&self) -> Vec ``` -------------------------------- ### Get First Alternative Frequency Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Retrieves the first decoded alternative frequency in Hz. Returns None if no valid alternative frequencies have been decoded yet. ```rust pub fn alt_frequency(&self) -> Option ``` -------------------------------- ### RdsResamplerCustom::new Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Constructs a new RdsResamplerCustom instance. It initializes two SincFixedOut resamplers for the in-phase (I) and quadrature (Q) components of the signal, configured for a specified input and output rate. ```APIDOC ## RdsResamplerCustom::new ### Description Initializes a new RDS resampler with specified input and output rates. This involves setting up two sinc-based resamplers for the I and Q components of the signal, using cubic interpolation and a Blackman-Harris window. ### Parameters - **input_rate** (f32) - The input sample rate of the signal. - **output_rate** (f32) - The desired output sample rate. ### Returns - `Self` - A new instance of `RdsResamplerCustom`. ``` -------------------------------- ### AdaptiveResampler::new (with custom PI parameters) Source: https://docs.rs/fmradio/latest/src/fmradio/resampler.rs.html Creates a new adaptive resampler with custom PI controller parameters, allowing fine-tuning of the buffer management logic. ```APIDOC ## AdaptiveResampler::new (custom PI parameters) ### Description Create a new adaptive resampler with custom PI controller parameters. ### Arguments * `initial_ratio` (f64) - Initial resampling ratio * `adjustment_interval` (usize) - Adjustment frequency * `channels` (usize) - Number of channels * `target_fill` (f64) - Target buffer fill level (0.0-1.0) ``` -------------------------------- ### Reset DeemphasisFilter State Source: https://docs.rs/fmradio/latest/src/fmradio/fm.rs.html Resets the internal state of the de-emphasis filter by clearing the previous output sample. This is useful when starting a new signal stream. ```rust pub fn reset(&mut self) { self.prev_y = 0.0; } ``` -------------------------------- ### Get Decoding Statistics Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Retrieves internal statistics of the RDS decoder, including counts of decoded groups and errors. Returns a tuple of (u64, u32, u32, u32). ```rust pub fn stats(&self) -> (u64, u32, u32, u32) ``` -------------------------------- ### RdsResamplerCustom Process with Pilot Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Processes an input signal using pilot phases to produce coherent baseband I/Q signals. It mixes down to baseband using a 57 kHz carrier derived from the 19 kHz pilot, with an option to add a 90° phase shift for compensation. ```rust /// Process MPX signal with pilot phases to produce coherent baseband I/Q /// pilot_phases: phase of 19 kHz pilot at each sample (multiply by 3 for 57 kHz) pub fn process_with_pilot( &mut self, input: &[f32], pilot_phases: &[f64], ) -> (Vec, Vec) { // Mix down to baseband using pilot-derived 57 kHz carrier // 57 kHz = 3 × 19 kHz, so phase_57 = 3 × phase_19 // The RDS carrier may have a phase offset relative to the pilot // Try adding 90° (π/2) to compensate let mut i_mixed = Vec::with_capacity(input.len()); let mut q_mixed = Vec::with_capacity(input.len()); // Debug: print pilot phase statistics once static DEBUG_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); let count = DEBUG_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if count == 100 && pilot_phases.len() >= 10 { // Check pilot phase rate of change using average over several samples to avoid wrap issues let mut total_delta = 0.0; let mut valid_deltas = 0; for i in 1..pilot_phases.len().min(100) { let mut delta = pilot_phases[i] - pilot_phases[i - 1]; // Handle phase wrapping // ... (rest of the code for mixing and phase calculation) ``` -------------------------------- ### FIR Filter Coefficient Generation and Application Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Generates coefficients for a Finite Impulse Response (FIR) filter using the Blackman window and sinc function, then normalizes them. The `push` method applies this filter to an input sample using a ring buffer for convolution. ```rust impl FirFilter { /// Create a new FIR filter with the given taps and cutoff frequency. pub fn new(taps: usize, cutoff_freq: f32, sample_rate: f32) -> Self { let mut coeffs = Vec::with_capacity(taps); let mid = (taps / 2) as isize; let norm_cutoff = cutoff_freq / sample_rate; for n in 0..taps { let x = n as isize - mid; let sinc = if x == 0 { 2.0 * norm_cutoff } else { (2.0 * norm_cutoff * PI * x as f32).sin() / (PI * x as f32) }; // Blackman window let window = 0.42 - 0.5 * ((2.0 * PI * n as f32) / (taps as f32 - 1.0)).cos() + 0.08 * ((4.0 * PI * n as f32) / (taps as f32 - 1.0)).cos(); coeffs.push(sinc * window); } // Normalize let norm: f32 = coeffs.iter().sum(); for v in coeffs.iter_mut() { *v /= norm; } Self { coeffs, buffer: vec![0.0; taps], write_pos: 0, } } fn push(&mut self, sample: f32) -> f32 { // Add sample to ring buffer self.buffer[self.write_pos] = sample; self.write_pos = (self.write_pos + 1) % self.buffer.len(); // Compute filtered output (convolution) let mut acc = 0.0_f32; let len = self.coeffs.len(); for i in 0..len { let buf_idx = (self.write_pos + len - 1 - i) % len; acc += self.buffer[buf_idx] * self.coeffs[i]; } acc } } ``` -------------------------------- ### Get Language Name from Code Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Converts a language code from Group 1A into its corresponding static string name. Returns None if the code is invalid or maps to an empty name. ```rust pub fn language_name(&self) -> Option<&'static str> { self.station_info.language_code.and_then(|code| { if (code as usize) < LANGUAGE_NAMES.len() { let name = LANGUAGE_NAMES[code as usize]; if !name.is_empty() { Some(name) } else { None } } else { None } }) } ``` -------------------------------- ### Get Current Radio Text (RT) Source: https://docs.rs/fmradio/latest/src/fmradio/rds/parser.rs.html Retrieves the Radio Text (RT) if at least two segments have been received. Returns None if the text is empty or insufficient segments are available. ```rust pub fn radio_text(&self) -> Option { let have = self.rt_received_mask.iter().filter(|&&x| x).count(); if have >= 2 { let rt_text = String::from_utf8_lossy(&self.rt).trim_end().to_string(); if !rt_text.is_empty() { Some(rt_text) } else { None } } else { None } } ``` -------------------------------- ### AdaptiveResampler Creation Source: https://docs.rs/fmradio/latest/src/fmradio/resampler.rs.html Creates a new AdaptiveResampler instance with a specified nominal ratio and channel count. Asserts that the creation is successful. ```rust let resampler = AdaptiveResampler::new(0.2, 1, 1); assert!(resampler.is_ok()); let resampler = resampler.unwrap(); assert_eq!(resampler.ratio(), 0.2); assert_eq!(resampler.nominal_ratio(), 0.2); ``` ```rust let resampler = AdaptiveResampler::new(0.2, 1, 2); assert!(resampler.is_ok()); ``` -------------------------------- ### Process I/Q Audio Input Source: https://docs.rs/fmradio/latest/fmradio/rds/dsp/struct.RdsDecoder.html Processes pre-mixed complex I/Q baseband audio input. This method bypasses NCO mixing and uses the pilot-derived 57 kHz carrier, assuming the input is already at 171 kHz and coherent with the pilot. ```rust pub fn process_iq(&mut self, input_i: &[f32], input_q: &[f32]) ``` -------------------------------- ### Initialize PhaseExtractor Source: https://docs.rs/fmradio/latest/src/fmradio/fm.rs.html Creates a new PhaseExtractor instance. The initial state `last` is set to a complex number representing 1.0 + 0.0i. ```rust use fmradio::fm::PhaseExtractor; use num_complex::Complex; let extractor = PhaseExtractor::new(); assert_eq!(extractor.last, Complex::new(1.0, 0.0)); ``` -------------------------------- ### Get All Alternative Frequencies Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Returns a vector of all valid alternative frequencies (AFs) decoded from the RDS signal, with each frequency converted to Hertz. Valid AFs are in the range of 1-204 (in 10kHz units). ```rust /// Return all alternative frequencies as a `Vec` in Hz pub fn alt_frequencies(&self) -> Vec { self.rds_parser .station_info() .af_list .iter() .map(|&f| u32::from(f) * 10_000) // Convert to Hz } ``` -------------------------------- ### RdsResamplerCustom Initialization Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Initializes a complex resampler for RDS signals, converting input rate to output rate. It configures sinc interpolation parameters, including filter length, cutoff frequency, interpolation type, oversampling factor, and window function. ```rust impl RdsResamplerCustom { pub fn new(input_rate: f32, output_rate: f32) -> Self { let ratio = output_rate as f64 / input_rate as f64; let params_i = SincInterpolationParameters { sinc_len: 128, // Good quality for this ratio f_cutoff: 0.9, // Cutoff just below Nyquist interpolation: SincInterpolationType::Cubic, oversampling_factor: 128, window: WindowFunction::BlackmanHarris2, }; let params_q = SincInterpolationParameters { sinc_len: 128, f_cutoff: 0.9, interpolation: SincInterpolationType::Cubic, oversampling_factor: 128, window: WindowFunction::BlackmanHarris2, }; // Output 1024 frames at a time (1 channel each for I and Q) let resampler_i = SincFixedOut::::new(ratio, 1.1, params_i, 1024, 1).unwrap(); let resampler_q = SincFixedOut::::new(ratio, 1.1, params_q, 1024, 1).unwrap(); debug!( "[RDS-RESAMP] Created {}kHz → {}kHz complex resampler (ratio: {:.4})", input_rate / 1000.0, output_rate / 1000.0, ratio ); Self { resampler_i, resampler_q, leftover_i: Vec::new(), leftover_q: Vec::new(), input_rate, } } // ... other methods ... ``` -------------------------------- ### Get First Alternative Frequency Source: https://docs.rs/fmradio/latest/src/fmradio/rds/dsp.rs.html Retrieves the first valid alternative frequency (AF) decoded from the RDS signal, converted to Hertz. Valid AFs are in the range of 1-204 (in 10kHz units). ```rust /// Get the first alternative frequency (if available) in Hz /// Returns None if no valid alternative frequencies are decoded yet pub fn alt_frequency(&self) -> Option { let af_list = &self.rds_parser.station_info().af_list; // Valid AF frequencies are 1-204 (in 10kHz units) // 225-249 are special codes, 0 is filler af_list .iter() .find(|&&f| (1..=204).contains(&f)) .map(|&f| u32::from(f) * 10_000) // Convert to Hz } ```