IMBE parameter codec — the 88-bit frame ↔︎ MBE vocoder parameters
The bridge that makes P25 voice fully native: P25's 88-bit IMBE parameter frame decoded to the (f₀, per-band voiced/unvoiced, harmonic amplitudes) triple the native MBE vocoder synthesises — and encoded back. With p25.voice's Golay/Hamming/PN FEC around it, the path from C4FM voice frame to PCM contains no mbelib.
Rafe project · app/radio/imbe.py, test_imbe.py · encode + decode + synthesis · fundamental/harmonic-count relation spec-accurate to IMBE (TIA-102.BABA); amplitude quantisation structurally IMBE, self-consistent — the exact amplitude tables + inter-frame DCT prediction remain for bit-exact on-air audio
Abstract
The P25 spec documents how a voice superframe yields, per 20 ms, eight FEC-protected information vectors u0..u7 totalling 88 bits of IMBE model parameters. The MBE vocoder spec documents how \((f_0, \{v_\ell\}, \{A_\ell\})\) becomes audio. This module is the layer between: the 88-bit frame's field layout and quantisers. Two parts are transcribed exactly from the IMBE definition; one part is structurally faithful and awaiting table transcription — the split is stated precisely below, so nobody mistakes which numbers are protocol facts.
1. Frame layout
An 88-bit parameter frame, and its P25 u-vector split:
bits 0–7 b0 — fundamental-frequency index (0–207)
bits 8–8+K−1 V/UV bits, one per 3-harmonic band, K = ⌈L/3⌉
bits 8+K–87 spectral amplitudes: 6-bit gain + per-harmonic residuals
u-vector sizes (frame_to_u / u_to_frame): [12, 12, 12, 12, 11, 11, 11, 7]
The u-split is exact and self-inverse (asserted in tests); it is the interface p25.voice's FEC layer produces and consumes.
2. Fundamental frequency and harmonic count — spec-accurate
The IMBE relation (TIA-102.BABA), implemented verbatim:
\[\omega_0 = \frac{4\pi}{b_0 + 39.5}\ \text{rad/sample},\qquad L = \left\lfloor 0.9254 \left\lfloor \frac{\pi}{\omega_0} - 0.25 \right\rfloor \right\rfloor\]
with \(b_0\) clamped to 0–207 and \(L\) to 1–56. Low pitch → small \(\omega_0\) → many harmonics (up to 56); high pitch → few (8). The inverse (f0_to_b0) rounds \(4\pi/\omega_0 - 39.5\). At \(f_s = 8000\) Hz, \(f_0 = \omega_0 f_s / 2\pi\). Note the crucial consequence, honoured by the encoder: \(L\) is a function of \(b_0\) alone — the amplitude and voicing fields' sizes are derived from the pitch field, never signalled separately.
3. Voicing and amplitude quantisation — structurally IMBE
- V/UV: one bit per band of 3 harmonics (\(K = \lceil L/3 \rceil\) bands). Encoding takes the majority vote of the band's per-harmonic flags; decoding replicates the band bit to its harmonics.
- Amplitudes:
encode_ampsworks in log₂ magnitude. A 6-bit gain (the mean log-amplitude, quantised over −10…+6) plus, per harmonic, the residual (log-amplitude minus gain) quantised over ±3 with \(\max(1, \lfloor(n_{\text{bits}} - 6)/L\rfloor)\) bits — the bit budget \(n_{\text{bits}} = 88 - 8 - K\) self-allocates: few harmonics → fine per-harmonic resolution, many harmonics → coarse. This is the IMBE scheme (gain + differential log magnitudes, bit-allocated by \(L\)); the published IMBE quantiser tables and the inter-frame DCT prediction are the named remaining transcription for bit-exact interop with real radios.
4. The fully-native P25 voice path
C4FM frame ─▶ p25.voice.decode_imbe (Golay(23,12)/Hamming(15,11) + PN)
─▶ u0..u7 ─▶ imbe.u_to_frame ─▶ decode_frame
─▶ (f0, voiced, amps) ─▶ mbe.synthesize ─▶ PCM
synthesize_u(u) performs the last three steps in one call. The end-to-end test drives the entire chain — encode a frame, wrap it in the real P25 voice FEC, inject 8 channel bit errors, decode — and asserts the u-vectors are recovered exactly and finite, non-silent PCM emerges. No mbelib anywhere.
5. Validation
| Test | Asserts |
|---|---|
test_fundamental_mapping_range |
b0 0→207 spans 8→56 harmonics, monotone the right way |
test_f0_recovery |
100–300 Hz round-trip within 5 % |
test_amplitude_and_vuv_recovery |
amplitude corr > 0.7 (coarser at high L, by design), V/UV agreement > 85 % |
test_frame_is_88_bits_and_u_split |
frame length exact; u split/join is the identity |
test_full_native_p25_voice_chain |
FEC corrects 8 channel errors; frame → PCM natively |
6. Scope notes
- The P25 spec's statement that IMBE→PCM is delegated to mbelib predates this module and is superseded:
p25.voicenow synthesises throughimbe.py+mbe.pynatively. - The same layering (framing → 88-bit-class parameter frame → MBE synthesis) is the template for the AMBE family (DMR/D-STAR/YSF/NXDN): each mode needs its framing and its own parameter mapping; AMBE+2 stays out of scope (in-force patents).
Related: P25 (the FEC and framing around this codec), MBE vocoder (the synthesis model beneath it), and the maths guide on source coding.