Skip to main content

Vocoder

Struct Vocoder 

Source
pub struct Vocoder { /* private fields */ }
Expand description

Chip-shaped façade over the rate-specific encoder + decoder + synth pipelines.

Owns all per-rate state internally (analysis, decoder, synth). One Vocoder is both encoder and decoder for a single channel direction; consumers running bidirectional voice typically allocate two — one each direction — to mirror the chip’s per-direction state isolation.

State is not Sync; one channel = one thread. State is Send, so the channel can move between threads.

Implementations§

Source§

impl Vocoder

Source

pub fn new(rate: Rate) -> Self

Open a new channel at the given rate, all state cold.

The unvoiced noise generator is selected per-rate: IMBE full-rate uses the BABA-A §1.12.1 spec LCG (171/11213/53125, seed 3147) because the DVSI chip’s full-rate path matches that recurrence; AMBE+2 half-rate uses the chip-empirical LCG (173/13849/65536, seed 60584) — see gap report 0025. Probe 1 confirmed 0.945 sample-level correlation against the live chip on the half-rate path.

Source

pub fn set_ambe_plus2_synth(&mut self, gen: AmbePlus2Synth)

Configure which half-rate synth flavor Self::decode_bits + Self::synthesize_params use on Phase 2 / AMBE+2 input. No-op for full-rate (which always uses the BABA-A §1.12 baseline IMBE synth). Default AmbePlus2Synth::AmbePlus.

Source

pub fn ambe_plus2_synth(&self) -> AmbePlus2Synth

Currently configured half-rate synth flavor.

Source

pub fn set_tone_detection(&mut self, enabled: bool)

Enable encode-side Annex T tone detection. Each PCM frame is inspected for a clean single-frequency tone or DTMF / Knox pair matching an Annex T entry; on a hit the detection result is surfaced via AnalysisStats::tone_detect. Default off.

Wire behavior is rate-dependent:

  • Half-rate (AMBE+2 Phase 2): on a hit, the channel emits an Annex T tone frame instead of running the voice analysis pipeline; AnalysisStats::output is Tone { id, amplitude } and AnalysisStats::tone_detect mirrors the same values.
  • Full-rate (IMBE Phase 1): the wire frame remains a regular voice / silence frame because P25 Phase 1 has no tone-frame opcode at the codec layer (BABA-A; spec-author confirmation 2026-04-30). AnalysisStats::tone_detect is still populated so application-layer consumers can route (I_D, A_D) to LCW, paging, or other out-of-band signaling per BABA-A §5.4.

Spec context: BABA-A §2.10 defines the tone-frame payload (Annex T table + signature bits) but the spec leaves “is this a tone?” to the implementer (per §0.0.1 of the impl spec). This is a DSP design choice, not a P25-IP question.

Source

pub fn tone_detection(&self) -> bool

Whether encode-side tone detection is currently enabled.

Source

pub fn set_repeat_reset_after(&mut self, n: Option<u32>)

Configure the beyond-spec consecutive-repeat reset threshold on the synth side. After n consecutive Repeat/Mute frames, substitution falls back to a default-fundamental + amps=1.0 frame instead of replaying the prior last_good. None (default) is the spec-faithful path per gap 0022 resolution. JMBE / SDRTrunk use Some(3) for chip-stream interop quality.

Source

pub fn repeat_reset_after(&self) -> Option<u32>

Current consecutive-repeat reset threshold (None = disabled, spec-faithful).

Source

pub fn set_chip_compat(&mut self, on: bool)

Enable JMBE-style error-rate freeze on Repeat (beyond-spec; gap 0021). When enabled, frames whose disposition is Repeat do not advance ε_R; the previous frame’s value is carried forward. This mirrors JMBE’s IMBEModelParameters.copy() calling setErrorRate(previous.getErrorRate()) and prevents runs of high-error chip-encoded frames from driving ε_R past the 0.0875 Mute threshold. Default false is the spec-faithful path. Enable for decoding chip-encoded P25 air traffic; leave off for our-encoder → our-decoder loops and spec-conformance testing.

Source

pub fn chip_compat(&self) -> bool

Current chip-compat (error-rate freeze on Repeat) setting.

Source

pub fn set_chip_compat_spectral_clamp(&mut self, on: bool)

Force the beyond-spec spectral-discontinuity clamp on (gap 0026) independently of Self::set_chip_compat. Most consumers should leave this off — the umbrella chip_compat flag already auto-enables the clamp alongside the gap-0021 ε_R freeze. This standalone toggle exists for the narrow case of wanting the clamp without the ε_R freeze. With either flag enabled, frames whose harmonic count L jumps by more than 5 from the previous frame (up-jumps only) or whose err.epsilon_0 ≥ 2 have their post-smoothing amplitudes scaled by 0.73 for one frame. Mirrors observed DVSI AMBE-3000R behavior on pitch/L jumps and Repeat frames; reduces audible “scratch” on noisy Phase 2 traffic where Golay-corrected frames decode to spectrally-distant params from their neighbors. Default false keeps the spec-faithful path.

Source

pub fn chip_compat_spectral_clamp(&self) -> bool

Current standalone spectral-discontinuity clamp setting. Note: the clamp also fires whenever Self::chip_compat is true, regardless of this setting.

Source

pub fn set_silence_dispatch(&mut self, on: bool)

Enable the §0.8.4 silence-dispatch path on the analysis encoder. When on, frames whose energy clears the silence detector’s hysteresis emit AnalysisOutput::Silence (rate-appropriate silence params) instead of running the full pitch / V-UV / amplitude pipeline. Default off — pass-through per addendum §0.8.8 recommendation.

Source

pub fn silence_dispatch(&self) -> bool

Whether silence dispatch is currently enabled.

Source

pub fn set_pitch_silence_override(&mut self, on: bool)

Enable the joint-signal silence override — dispatches pitch-unreliable low-energy frames as silence even if they don’t reach the §0.8.4 hysteresis threshold. Requires Self::set_silence_dispatch also be on. Default off.

Source

pub fn pitch_silence_override(&self) -> bool

Whether the joint-signal silence override is currently enabled.

Source

pub fn set_default_pitch_on_silence(&mut self, on: bool)

Enable or disable the onset-attack mitigation. On near-silent frames the encoder commits a short default pitch (≈ 25 samples / ~320 Hz) to its look-back history instead of the phantom long-period autocorrelation peak that quiet input otherwise produces. The silent frame still encodes through the full voice pipeline; only the next frame’s look_back continuity window is affected. Targets the 2-frame onset attack at silence→loud transitions documented in project_onset_attack_2frame_2026-04-25.md. Default off — eval via the 5-vector PESQ harness before enabling in production.

Source

pub fn default_pitch_on_silence(&self) -> bool

Whether the onset-attack mitigation is currently enabled.

Source

pub fn set_pyin_pitch(&mut self, on: bool)

Enable or disable the PYIN pitch frontend. When on, the analysis encoder uses crate::codecs::mbe_baseline::analysis::run_pyin for (p_hat_i, e_p_hat_i) instead of the §0.3 look-back / look-ahead tracker (Eq. 5–23). PYIN is post-2002 DSP outside BABA-A’s clean-room scope; landed for A/B PESQ evaluation. The §0.3 path remains the default and the spec-faithful baseline.

Source

pub fn pyin_pitch(&self) -> bool

Whether the PYIN pitch frontend is currently enabled.

Source

pub fn set_spectral_subtraction(&mut self, on: bool)

Enable or disable input-side spectral subtraction (Boll 1979) applied to signal_spectrum output before §0.5 amplitude estimation. Per-bin running noise PSD is updated on silence-flagged frames; voiced frames hold. Off by default — targets noisy-tone inputs (memo project_amp_noise_sensitivity_2026-04-24).

Source

pub fn spectral_subtraction(&self) -> bool

Whether spectral subtraction is currently enabled.

Source

pub fn set_amp_ema_alpha(&mut self, alpha: f64)

Set the §0.5 amplitude EMA weight α. 0.0 disables the smoother (default); (0.0, 1.0] enables M̂_l(t) = α · M̂_l + (1−α) · M̂_l(t−1) per harmonic, gated by pitch similarity. Targets the noisy-tone amp jitter described in project_amp_noise_sensitivity_2026-04-24. Outside BABA-A clean-room scope (general-DSP magnitude smoothing).

Source

pub fn amp_ema_alpha(&self) -> f64

Current §0.5 amplitude EMA weight; 0.0 means the smoother is off.

Source

pub fn set_enhancement(&mut self, mode: EnhancementMode)

Configure the post-decoder enhancement chain. Off by default (EnhancementMode::None — spec-faithful PCM). When set to EnhancementMode::Classical, decoded PCM passes through a biquad cascade + soft-knee compressor + boundary-fade chain before Self::decode_bits returns. See crate::enhancement for stage details and AIC33 mapping.

Resets the chain’s runtime filter state (delay lines, envelope, pending fade) so the new mode starts clean. Persistent: stays configured across Self::reset.

Source

pub fn enhancement(&self) -> &EnhancementMode

Currently configured enhancement mode.

Source

pub fn builder(rate: Rate) -> VocoderBuilder

Start a fluent builder for this rate. Equivalent to VocoderBuilder::new(rate).

Source

pub fn rate(&self) -> Rate

The rate this channel was constructed at. Cannot change for the lifetime of the channel; build a new Vocoder to switch rates (mirrors a chip’s PKT_RATEP cycle).

Source

pub fn frame_samples(&self) -> usize

Number of i16 samples consumed per Self::encode_pcm call, and produced per Self::decode_bits call.

Source

pub fn fec_frame_bytes(&self) -> usize

Number of FEC bytes per encoded frame at this rate.

Source

pub fn last_stats(&self) -> &FrameStats

Read the most recent frame’s stats. Returns the zero-default before any frame has been processed.

Source

pub fn last_disposition(&self) -> Option<FrameDisposition>

Disposition (Use / Repeat / Mute) of the most recent Self::decode_bits call’s synthesizer pass. None until at least one frame has been decoded. Reset by Self::reset.

Decode-side only; encode-side has no synth and always returns None.

Source

pub fn reset(&mut self)

Reset all channel state. Equivalent to the chip’s PKT_RATEP re-send: clears predictor history, decoder predictor state, synth substates, smoothed error rate, last-stats. Rate and configuration knobs (tone detection) stay the same.

Source

pub fn encode_pcm(&mut self, pcm: &[i16]) -> Result<Vec<u8>, VocoderError>

Encode one PCM frame into FEC-encoded bytes.

pcm must be exactly Self::frame_samples samples (160). Returns exactly Self::fec_frame_bytes bytes.

Source

pub fn decode_bits(&mut self, bits: &[u8]) -> Result<Vec<i16>, VocoderError>

Decode one FEC-encoded frame into PCM samples.

bits must be exactly Self::fec_frame_bytes bytes. Returns exactly Self::frame_samples PCM samples.

Source

pub fn encode_stream<'a>(&'a mut self, pcm: &'a [i16]) -> EncodeStream<'a>

Encode an arbitrary-length PCM slice as a stream of frames.

Returns an iterator that yields one Result<Vec<u8>> per frame consumed (160 samples per frame). Trailing partial frames are silently dropped — the caller is responsible for padding to a multiple of Self::frame_samples if all samples must be encoded.

State (predictor, look-ahead history, ε_R) advances across frames just as it would with manual per-frame Self::encode_pcm calls.

let mut tx = Vocoder::new(Rate::Imbe7200x4400);
let bits: Result<Vec<Vec<u8>>, _> = tx.encode_stream(&pcm).collect();
assert_eq!(bits.unwrap().len(), 5);
Source

pub fn extract_params(&mut self, pcm: &[i16]) -> Result<MbeParams, VocoderError>

Run the analysis encoder on one PCM frame and return the resulting MbeParams without quantizing or FEC-encoding. Advances the analysis state (look-ahead history, predictor, V/UV state, silence detector) so subsequent calls see continuous context.

pcm must be exactly Self::frame_samples samples (160).

On AnalysisOutput::Silence (preroll, silence dispatch, half-rate PitchOutOfRange), returns the rate-appropriate silence params (MbeParams::silence or MbeParams::silence_ambe_plus2).

Counterpart of Self::synthesize_params. Together these expose the parameter layer without going through wire FEC, enabling rate-conversion / analysis / playback pipelines that don’t need the full encode/decode round-trip.

Source

pub fn synthesize_params(&mut self, params: &MbeParams) -> Vec<i16>

Synthesize one PCM frame directly from MbeParams, skipping the FEC + dequantize chain. Advances the channel’s synth state (so re-acquisition after silence and across-frame phase continuity work the same way they would for a normal Self::decode_bits call).

Useful for playing back tone-frame params from crate::ambe_plus2_wire::dequantize::tone_to_mbe_params, replaying captured params in test harnesses, or driving synth from any upstream that produces MbeParams without going through wire bits.

The dispatch is rate-aware: full-rate uses BABA-A baseline phase; half-rate uses AMBE+2 (US5701390) phase regen, matching what Self::decode_bits would do.

last_stats is not updated by this call — it’s reserved for the wire-aware encode/decode paths. Read the synth’s disposition via Self::last_disposition which IS advanced (the disposition reflects this synth call).

Source

pub fn decode_stream<'a>(&'a mut self, bits: &'a [u8]) -> DecodeStream<'a>

Decode an arbitrary-length FEC byte slice as a stream of PCM frames.

Returns an iterator that yields one Result<Vec<i16>> per frame consumed (Self::fec_frame_bytes per frame). Trailing partial frames are silently dropped.

let mut rx = Vocoder::new(Rate::Imbe7200x4400);
let pcm_frames: Result<Vec<Vec<i16>>, _> = rx.decode_stream(&bits).collect();
assert_eq!(pcm_frames.unwrap().len(), 5);

Trait Implementations§

Source§

impl Debug for Vocoder

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.