Field Guide · term

Also known as: Post_Process, output scaling stage, x2 post-processing

ACELP Post_Process is the final output stage of the TETRA ACELP decoder specified by ETSI EN 300 395-2: a saturating multiply-by-two applied to every decoded PCM sample. It carries no spectral or excitation information — it exists solely to put the decoder’s output at the reference level. GopherTrunk applies it in the vocoder wrapper’s Decode method, and without it the rendered audio sits a full 6 dB below the ETSI reference decoder.1

+full scale (clamp) raw decoder after saturating ×2 ×2
Post_Process doubles every output sample with saturation: mid-scale samples gain 6 dB, while any sample that would overshoot is clamped to full scale rather than wrapping.

What the stage does

The reference codec’s internal synthesis produces PCM at half the intended playback level, so the last thing Decod_Tetra does is double it. GopherTrunk’s vocoder.Decode mirrors this exactly: after the raw synthesis returns 240 int16 samples (30 ms at 8 kHz), it runs each sample through addOp(sfin, sfin) — adding the sample to itself, which is a multiply-by-two using the G.191 saturating add. The result is one call’s worth of PCM at the reference level, ready for the downstream DC block, AGC, and playback.

Six decibels is a factor of two in amplitude, which is exactly what a ×2 buys — so an implementation that renders otherwise-correct TETRA audio but sounds conspicuously quiet has almost always dropped this stage. It is a small, easy-to-miss line that is nonetheless part of the bit-exact contract with the reference decoder.

Why saturation, not a plain multiply

Doubling a value that is already near the top of the int16 range would overflow. In ordinary two’s-complement arithmetic that overflow wraps: a large positive sample flips to a large negative one, producing a loud click on every loud sample — the worst possible artifact on exactly the samples a listener notices most. Saturation arithmetic avoids this by clamping instead of wrapping: any product that exceeds +32767 pins to +32767, and any below −32768 pins to −32768. The clamp introduces a little harmonic distortion on peaks, but that is inaudible next to the full-scale sign inversion wrapping would cause. This is the same fixed-point discipline the entire ACELP decoder is built on — every intermediate operation saturates rather than wraps, so the whole signal path stays bounded and matches the reference bit for bit.

Using addOp rather than a bare int16(sfin * 2) is deliberate: addOp is the reference’s add operator, which sets the saturation clamp and the global overflow flag exactly as the C codec does. Substituting a native multiply would silently reintroduce the wrap-around the standard was written to prevent.

Where it sits in the chain

Post_Process is the boundary between the ACELP codec proper and GopherTrunk’s audio output path. Upstream, the TCH/S speech-frame decode and the ACELP synthesis produce the raw PCM; Post_Process lifts it to reference level; downstream, generic audio conditioning takes over. Placing the ×2 at the codec’s own output — rather than folding it into a later gain stage — keeps GopherTrunk’s decode faithful to the reference at the exact point the standard specifies, so the codec’s output can be compared sample-for-sample against ETSI’s before any GopherTrunk-specific processing muddies the comparison.

Relevance to SDR

For a scanner the practical payoff is consistent, correct loudness: TETRA calls decode at the same level a reference-conformant radio would produce, so they sit sensibly alongside other protocols in a multi-system scanner without a per-protocol volume fudge. Because the stage is a fixed part of the reference algorithm, GopherTrunk verifies it as part of the ACELP decoder’s end-to-end conformance rather than tuning it by ear.

Sources

  1. Saturation arithmetic — Wikipedia, on clamping versus wrap-around overflow in fixed-point audio arithmetic. 

See also