mcelp 1.0.1

Mitsubishi CELP speech codec: a 3.6 kbit/s speech encoder and decoder
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
//! The decoder: everything it carries between frames, and the frame loop
//! that ties the stages together.
//!
//! A frame is decoded half at a time.  Each half re-derives its two subframe
//! LPC sets by interpolating LSPs, builds and synthesises the two subframes,
//! then postfilters them and runs the result through the output filter.  The
//! synthesised-speech and excitation buffers are only one half-frame long and
//! are slid along as each half completes.

use crate::LPC_ORDER as ORDER;
use crate::bitstream::{self, Params};
use crate::codebook;
use crate::excitation;
use crate::fixed::{acc, hi, sat, shift};
use crate::gain::{self, GainState};
use crate::lsp::{self, LsfState};
use crate::pitch::{self, Lag, SUBFRAME};
use crate::postfilter;
use crate::synth;
use crate::ulaw;

/// Samples per frame.
pub use crate::FRAME;
/// Excitation history kept before the current half-frame.
pub const EXC_HISTORY: usize = crate::EXCITATION_HISTORY;
/// Half a frame.
pub use crate::HALF;
/// Residual history the pitch postfilter searches over.
pub use crate::ltp::RES_HISTORY;

/// Everything that has to survive from one frame to the next.
#[derive(Clone)]
pub struct Decoder {
    /// LSF quantiser memory.
    pub(crate) lsf: LsfState,
    /// Gain quantiser memory.
    pub(crate) gain: GainState,
    /// LSP set of the previous half-frame, one end of the interpolation.
    pub(crate) prev_lsp: [i16; ORDER],
    /// `1/A(z)` filter memory.
    pub(crate) synth_mem: [i16; ORDER],
    /// Excitation: `EXC_HISTORY` past samples followed by the current half.
    pub(crate) exc: [i16; EXC_HISTORY + HALF],
    /// Synthesised speech: ten past samples followed by the current *half*
    /// frame, which is all the buffer the reference keeps.
    pub(crate) speech: [i16; ORDER + HALF],
    /// State of the short-term adaptive postfilter.
    pub(crate) short_term_filter: postfilter::ShortTermFilter,
    /// Output reconstruction filter.
    pub(crate) output_filter: crate::postfilter::OutputFilter,
    /// Pitch lag of the half-frame's first subframe, which the long-term
    /// postfilter searches around.
    pub(crate) reference_lag: i16,
    /// Last subframe lag of the previous half-frame.
    pub(crate) last_half_lag: i16,
    /// LPC residual the postfilter works on: history plus the current subframe.
    pub(crate) residual: [i16; RES_HISTORY + SUBFRAME],
    /// Sharpening gain used by fixed-codebook classes 0, 3 and 4.
    pub(crate) sharpen_fixed: i16,
    /// Sharpening gain used by classes 1, 2 and 5; doubles as a voicing flag.
    pub(crate) sharpen_voiced: i16,
    /// Pitch lag carried across frames for erasure concealment.
    pub(crate) conceal_lag: i16,
    /// Pitch lag of the previous subframe, for the lag-continuity test.
    pub(crate) prev_lag: i16,
    /// Latest non-zero long-term postfilter decision, read by the next
    /// half-frame's excitation build.
    pub(crate) voiced: i16,
    /// Whether LSP interpolation is currently disabled.
    pub(crate) no_interp: i16,
    /// State of the 32-bit LCG used to fill erased frames.
    pub(crate) rng: u32,
}

impl Default for Decoder {
    /// The reset state.
    fn default() -> Self {
        let mut prev_lsp = [0i16; ORDER];
        prev_lsp.copy_from_slice(&crate::tables::LSP_MEAN[..ORDER]);
        Decoder {
            lsf: LsfState::default(),
            gain: GainState::default(),
            prev_lsp,
            synth_mem: [0; ORDER],
            exc: [0; EXC_HISTORY + HALF],
            speech: [0; ORDER + HALF],
            short_term_filter: postfilter::ShortTermFilter::default(),
            output_filter: crate::postfilter::OutputFilter::default(),
            reference_lag: 60,
            last_half_lag: 60,
            residual: [0; RES_HISTORY + SUBFRAME],
            sharpen_fixed: 3277,
            sharpen_voiced: 3277,
            conceal_lag: 60,
            prev_lag: 60,
            voiced: 60,
            no_interp: 0,
            rng: 21845,
        }
    }
}

/// Lower clamp on the pitch gain when it is reused for pitch sharpening, Q14.
const SHARPEN_MIN: i16 = 3277;
/// Upper clamp on the same, Q14.
const SHARPEN_MAX: i16 = 13017;
/// Multiplier of the pseudo-random generator used to fill erased frames.
const RNG_MULTIPLIER: u16 = 31821;
/// Increment of the same generator.
const RNG_INCREMENT: i16 = 13849;

/// Mix one adaptive and fixed-codebook sample under the current loss mode.
fn excitation_sample(
    suppressed: bool,
    voiced: bool,
    innovation: i16,
    adaptive: i16,
    gains: gain::Gains,
) -> i16 {
    if !suppressed {
        return excitation::mixed_sample(adaptive, innovation, gains.pitch, gains.code);
    }
    let value = if voiced {
        sat(acc(shift(
            acc((gains.pitch as i64) * (adaptive as i64) * 2),
            1,
        ) + (1 << 15)))
    } else {
        sat(acc(shift(
            acc((gains.code as i64) * (innovation as i64) * 2),
            2,
        ) + (1 << 15)))
    };
    hi(value)
}

impl Decoder {
    /// A decoder in its reset state.
    pub fn new() -> Self {
        Self::default()
    }

    /// Decode one transport frame into 320 mu-law samples.
    ///
    /// Returns `None` for an in-band reset frame, which resets the decoder
    /// instead of producing audio; every other frame yields a full frame of
    /// samples, including one flagged as erased, which is concealed.
    pub fn decode(&mut self, frame: &[u8; bitstream::FRAME_BYTES]) -> Option<[u8; FRAME]> {
        let linear = self.decode_linear(frame)?;
        let mut out = [0u8; FRAME];
        ulaw::frame_from_linear(&linear, &mut out);
        Some(out)
    }

    /// The same, as linear 16-bit samples rather than mu-law.
    ///
    /// The codec is mu-law end to end, so [`decode`](Self::decode) is what the
    /// reference produces; this returns the samples one step earlier, before
    /// the output companding, for callers that want linear PCM.
    pub fn decode_linear(&mut self, frame: &[u8; bitstream::FRAME_BYTES]) -> Option<[i16; FRAME]> {
        let words = bitstream::canonicalize(frame);
        if bitstream::is_reset(&words) {
            *self = Decoder::default();
            return None;
        }
        let params = bitstream::unpack(&words);
        Some(self.decode_params(&params))
    }

    /// Decode and postfilter one half-frame into its output slice.
    fn decode_output_half(
        &mut self,
        params: &Params,
        half: usize,
        half_lsp: &[i16; ORDER],
        output: &mut [i16],
    ) {
        let subframe_lsp = self.decode_half(params, half, half_lsp);
        self.voiced = 0;
        for (sub, lsp) in subframe_lsp.iter().enumerate() {
            let at = sub * SUBFRAME;
            let lag = self.postfilter_subframe(lsp, at, &mut output[at..at + SUBFRAME]);
            if lag != 0 {
                self.voiced = lag;
            }
        }

        // The last ten samples of the half become the next half's history.
        let tail = ORDER + HALF - ORDER;
        self.speech.copy_within(tail..tail + ORDER, 0);
        self.output_filter.run(output);
    }

    /// Decode from already unpacked parameters into linear samples.
    pub fn decode_params(&mut self, params: &Params) -> [i16; FRAME] {
        let (first_half_lsp, second_half_lsp) = self.decode_spectrum(params);

        let mut linear = [0i16; FRAME];
        for (half, half_lsp) in [first_half_lsp, second_half_lsp].iter().enumerate() {
            let at = half * HALF;
            self.decode_output_half(params, half, half_lsp, &mut linear[at..at + HALF]);
        }
        linear
    }

    /// Test hook: run the spectral stage in isolation.
    #[doc(hidden)]
    pub fn dump_spectrum(&mut self, params: &Params) -> ([i16; ORDER], [i16; ORDER]) {
        self.decode_spectrum(params)
    }

    /// Test hook: run one half-frame in isolation.
    /// Test hook: the persistent state, formatted for diffing against the
    /// instrumented reference.
    #[doc(hidden)]
    pub fn dump_state(&self) -> String {
        format!(
            "voiced={} gains={:?} erasures={} lag={} rng={:08x}\nEXC {:?}\nSPEECH {:?}",
            self.voiced,
            self.gain.gains,
            self.gain.erasures,
            self.conceal_lag,
            self.rng,
            &self.exc[..],
            &self.speech[..],
        )
    }

    #[doc(hidden)]
    pub fn dump_half(&mut self, params: &Params, half: usize, lsp: &[i16; ORDER]) {
        self.decode_half(params, half, lsp);
    }

    /// Decode the frame's spectral parameters.
    ///
    /// Returns the LSP sets the two half-frames should aim at.
    fn decode_spectrum(&mut self, params: &Params) -> ([i16; ORDER], [i16; ORDER]) {
        let lsf = if params.suppress {
            lsp::decode_suppressed(&mut self.lsf)
        } else {
            let idx = lsp::LsfIndices::unpack(params.field[0], params.field[1]);
            lsp::decode(&mut self.lsf, idx)
        };
        self.lsf.prev_lsf = lsf;
        let current = lsp::lsf_to_lsp(&lsf);

        let mut mid = lsp::midpoint(&self.prev_lsp, &current);
        let reflection = lsp::reflection_coefficients(&lsp::lsp_to_lpc(&current));
        let (flag, force_current) = lsp::interpolation_control(&reflection, self.no_interp != 0);
        if force_current {
            mid = current;
        }
        self.no_interp = flag as i16;
        (mid, current)
    }

    /// Build and synthesise one half-frame.
    ///
    /// Returns the per-subframe LSP and LPC sets, which the postfilter needs.
    fn decode_half(
        &mut self,
        params: &Params,
        half: usize,
        half_lsp: &[i16; ORDER],
    ) -> [[i16; ORDER]; 2] {
        let (lsp_a, lsp_b, lpc_a, lpc_b) = lsp::interpolate_pair(&self.prev_lsp, half_lsp);
        self.prev_lsp = *half_lsp;
        let subframe_lsp = [lsp_a, lsp_b];
        let subframe_lpc = [lpc_a, lpc_b];

        for (sub, subframe_lpc) in subframe_lpc.iter().enumerate() {
            self.decode_subframe(params, half, sub, subframe_lpc);
        }

        // Slide the excitation history along by one half-frame.
        self.exc.copy_within(HALF.., 0);
        self.last_half_lag = self.prev_lag;
        subframe_lsp
    }

    /// Decode and record the pitch lag for one subframe.
    fn decode_lag(&mut self, suppressed: bool, sub: usize, field: i16) -> Lag {
        let lag = if suppressed {
            let lag = Lag {
                integer: self.conceal_lag,
                frac: 0,
            };
            self.conceal_lag = (self.conceal_lag + 1).min(143);
            lag
        } else if sub == 0 {
            let lag = pitch::decode_absolute(field);
            self.conceal_lag = lag.integer;
            lag
        } else {
            let lag = pitch::decode_relative(field, self.prev_lag);
            self.conceal_lag = lag.integer;
            lag
        };
        if sub == 0 {
            self.reference_lag = lag.integer;
        }
        self.prev_lag = lag.integer;
        lag
    }

    /// Select the gains used to sharpen the two fixed-codebook class groups.
    fn update_sharpening(&mut self, lpc: &[i16; ORDER + 1], lag: &Lag) {
        let reflection = lsp::reflection_coefficients(lpc);
        (self.sharpen_fixed, self.sharpen_voiced) = excitation::interpolation_gains(
            reflection[0],
            self.sharpen_fixed,
            lag.integer,
            self.last_half_lag,
        );
    }

    /// Decode and pitch-sharpen the fixed-codebook contribution.
    fn decode_fixed_codebook(
        &mut self,
        suppressed: bool,
        field: i16,
        lag: &Lag,
    ) -> [i16; SUBFRAME] {
        let code_index = if suppressed {
            self.next_random()
        } else {
            field as u16
        };
        let innovation = codebook::decode(code_index);
        let mut code = innovation.code;
        if lag.integer < SUBFRAME as i16 {
            let sharpen = if innovation.class == 5 || (1..3).contains(&innovation.class) {
                self.sharpen_voiced
            } else {
                self.sharpen_fixed
            };
            pitch::sharpen(&mut code, lag, sharpen);
        }
        code
    }

    /// Decode the gain field and update the gain reused for pitch sharpening.
    fn decode_gains(
        &mut self,
        suppressed: bool,
        field: i16,
        code: &[i16; SUBFRAME],
    ) -> gain::Gains {
        let gains = if suppressed {
            gain::decode_suppressed(&mut self.gain)
        } else {
            gain::decode(&mut self.gain, field, code)
        };
        self.sharpen_fixed = gains.pitch.clamp(SHARPEN_MIN, SHARPEN_MAX);
        gains
    }

    /// Synthesize one excitation subframe and place it in the speech window.
    fn synthesise_subframe(&mut self, sub: usize, at: usize, lpc: &[i16; ORDER + 1]) {
        let mut out = [0i16; SUBFRAME];
        let Decoder { exc, synth_mem, .. } = self;
        synth::synthesis(lpc, &exc[at..at + SUBFRAME], &mut out, synth_mem);
        let dst = ORDER + sub * SUBFRAME;
        self.speech[dst..dst + SUBFRAME].copy_from_slice(&out);
    }

    /// Rebuild and synthesise one subframe from its three transmitted fields.
    fn decode_subframe(
        &mut self,
        params: &Params,
        half: usize,
        sub: usize,
        lpc: &[i16; ORDER + 1],
    ) {
        let at = EXC_HISTORY + sub * SUBFRAME;
        let field = params.subframe(half, sub);

        let lag = self.decode_lag(params.suppress, sub, field.lag);
        pitch::predict(&mut self.exc, at, &lag);

        self.update_sharpening(lpc, &lag);
        let code = self.decode_fixed_codebook(params.suppress, field.code, &lag);

        let gains = self.decode_gains(params.suppress, field.gain, &code);
        self.combine_excitation(params.suppress, at, &code, gains);
        self.synthesise_subframe(sub, at, lpc);
    }

    /// Mix the adaptive and fixed contributions into the excitation.
    ///
    /// While frames are being received normally both contributions are summed;
    /// during an erasure only one of them survives, chosen by whether the last
    /// long-term postfilter found a pitch.
    fn combine_excitation(
        &mut self,
        suppressed: bool,
        at: usize,
        code: &[i16; SUBFRAME],
        gains: gain::Gains,
    ) {
        let voiced = self.voiced != 0;
        for (&innovation, excitation) in code.iter().zip(self.exc[at..at + SUBFRAME].iter_mut()) {
            *excitation = excitation_sample(suppressed, voiced, innovation, *excitation, gains);
        }
    }

    /// Generate the numerator-filtered residual and append it to the pitch
    /// postfilter's history window.
    fn update_postfilter_residual(&mut self, numerator: &[i16; ORDER + 1], at: usize) {
        let speech = &self.speech[at..at + ORDER + SUBFRAME];
        let mut residual = [0i16; SUBFRAME];
        postfilter::inverse_filter(numerator, speech, &mut residual);
        self.residual[RES_HISTORY..].copy_from_slice(&residual);
    }

    /// Postfilter one subframe and emit it.
    ///
    /// Returns the pitch lag the long-term postfilter settled on, or zero when
    /// it decided to stay out of the way.
    fn postfilter_subframe(
        &mut self,
        subframe_lsp: &[i16; ORDER],
        at: usize,
        out: &mut [i16],
    ) -> i16 {
        let coefficients = postfilter::Coefficients::new(subframe_lsp);

        self.update_postfilter_residual(&coefficients.numerator, at);

        // Long-term postfilter.
        let mut filtered = [0i16; SUBFRAME];
        let lag = crate::ltp::filter(&self.residual, self.reference_lag, &mut filtered);

        let Decoder {
            speech,
            short_term_filter,
            ..
        } = self;
        let speech_block = &speech[ORDER + at..ORDER + at + SUBFRAME];
        let compensated = short_term_filter.filter(&coefficients, speech_block, filtered);
        out.copy_from_slice(&compensated);

        // Slide the residual history along.
        self.residual.copy_within(SUBFRAME.., 0);
        lag
    }

    /// One step of the linear congruential generator that stands in for a lost
    /// codebook index.
    fn next_random(&mut self) -> u16 {
        // The two halves are multiplied separately; both products pick up the
        // fractional-mode doubling, which the right shift on the low half then
        // takes back out.
        let upper = ((self.rng >> 16) as u16 as i64) * (RNG_MULTIPLIER as i64) * 2;
        let lower = ((self.rng as u16) as i64) * (RNG_MULTIPLIER as i64) * 2;
        let mixed = acc(shift(acc(upper), 15) + shift(lower, -1) + (RNG_INCREMENT as i64));
        self.rng = mixed as u32;
        self.rng as u16
    }
}