// Public API of ambe2.h over the halfrate codec (lib/halfrate). GPL-3.0-or-later. #include #include #include #include #include "ambe2.h" #include "halfrate.h" struct ambe2_encoder { hr_encoder *hr; }; struct ambe2_decoder { hr_decoder *hr; }; int ambe2_default_codec(void) { return AMBE2_CODEC_HALFRATE; } const char *ambe2_codec_name(int codec) { return codec == AMBE2_CODEC_HALFRATE ? "halfrate" : NULL; } //const char *ambe2_version(void) { return AMBE2_VERSION " halfrate " HALFRATE_VERSION; } // 49 bits in on-air order <-> halfrate's 7-byte frame (the same bits, packed MSB first) static void bits_to_hr(const uint8_t *bits, uint8_t *frame) { uint8_t b[AMBE2_BITS]; for (int i = 0; i < AMBE2_BITS; i++) b[i] = bits[i] & 1; hr_frame_from_bits(b, frame); } ambe2_encoder *ambe2_encoder_new_codec(int codec, int clipped) { if (codec != AMBE2_CODEC_HALFRATE) return NULL; ambe2_encoder *e = (ambe2_encoder *)calloc(1, sizeof *e); if (!e) return NULL; e->hr = hr_encoder_new(); if (!e->hr) { free(e); return NULL; } return e; } ambe2_encoder *ambe2_encoder_new(void) { return ambe2_encoder_new_codec(AMBE2_CODEC_HALFRATE, 0); } void ambe2_encoder_free(ambe2_encoder *e) { if (!e) return; hr_encoder_free(e->hr); free(e); } int ambe2_encoder_codec(const ambe2_encoder *e) { return AMBE2_CODEC_HALFRATE; } int ambe2_encoder_set_option(ambe2_encoder *e, const char *name, double value) { return e ? hr_encoder_set(e->hr, name, value) : -1; } void ambe2_encode(ambe2_encoder *e, const int16_t *pcm, uint8_t *bits) { uint8_t frame[HR_FRAME_BYTES]; hr_encode(e->hr, pcm, frame); hr_bits_from_frame(frame, bits); } static void to_pcm(const float *x, int16_t *pcm) { for (int i = 0; i < AMBE2_SAMPLES; i++) { double v = nearbyint((double)x[i] * 32768.0); pcm[i] = (int16_t)(v > 32767 ? 32767 : v < -32768 ? -32768 : v); } } // Frame k comes out of the encoder AMBE2_ENCODER_DELAY calls later; silence after the clip pushes the last ones out. size_t ambe2_encode_clip(ambe2_encoder *e, const float *x, size_t n, uint8_t *bits) { size_t frames = n / AMBE2_SAMPLES; int16_t pcm[AMBE2_SAMPLES]; uint8_t skip[AMBE2_BITS]; for (size_t t = 0; t < frames + AMBE2_ENCODER_DELAY; t++) { if (t < frames) to_pcm(x + t * AMBE2_SAMPLES, pcm); else memset(pcm, 0, sizeof pcm); ambe2_encode(e, pcm, t >= AMBE2_ENCODER_DELAY ? bits + (t - AMBE2_ENCODER_DELAY) * AMBE2_BITS : skip); } return frames; } // Streaming: raw samples wait in `pending` until a frame is whole; `encoded` counts encoder calls (with the silence of // the flush), `real` the frames of audio. The output of call k is frame k - AMBE2_ENCODER_DELAY. struct ambe2_stream { ambe2_encoder *e; std::vector pending; size_t encoded, real; }; ambe2_stream *ambe2_stream_new_codec(int codec, int clipped) { ambe2_encoder *e = ambe2_encoder_new_codec(codec, clipped); if (!e) return NULL; ambe2_stream *s = new ambe2_stream; s->e = e; s->encoded = s->real = 0; return s; } ambe2_stream *ambe2_stream_new(int clipped) { return ambe2_stream_new_codec(AMBE2_CODEC_HALFRATE, clipped); } void ambe2_stream_free(ambe2_stream *s) { if (s) { ambe2_encoder_free(s->e); delete s; } } int ambe2_stream_set_option(ambe2_stream *s, const char *name, double value) { return s ? ambe2_encoder_set_option(s->e, name, value) : -1; } // One encoder call; returns 1 when it gave a frame of the stream (written to bits). static int stream_encode(ambe2_stream *s, const int16_t *pcm, uint8_t *bits) { uint8_t skip[AMBE2_BITS]; size_t k = s->encoded++; bool out = k >= AMBE2_ENCODER_DELAY && k - AMBE2_ENCODER_DELAY < s->real; ambe2_encode(s->e, pcm, out ? bits : skip); return out; } static size_t stream_emit(ambe2_stream *s, uint8_t *bits, size_t max_frames, bool final) { size_t out = 0, used = 0; // a whole frame of audio is encoded only when its output (if any) has room while (s->pending.size() - used >= AMBE2_SAMPLES && (s->encoded < AMBE2_ENCODER_DELAY || out < max_frames)) { s->real++; out += stream_encode(s, &s->pending[used], bits + out * AMBE2_BITS); used += AMBE2_SAMPLES; } s->pending.erase(s->pending.begin(), s->pending.begin() + used); if (final && s->pending.size() < AMBE2_SAMPLES) { // (whole frames still waiting: no room, call again) s->pending.clear(); // a partial last frame is dropped, as in ambe2_encode_clip int16_t zero[AMBE2_SAMPLES] = {0}; while (out < max_frames && s->encoded < s->real + AMBE2_ENCODER_DELAY) out += stream_encode(s, zero, bits + out * AMBE2_BITS); } return out; } size_t ambe2_stream_push(ambe2_stream *s, const int16_t *pcm, size_t n, uint8_t *bits, size_t max_frames) { s->pending.insert(s->pending.end(), pcm, pcm + n); return stream_emit(s, bits, max_frames, false); } size_t ambe2_stream_flush(ambe2_stream *s, uint8_t *bits, size_t max_frames) { return stream_emit(s, bits, max_frames, true); } // halfrate decoder settings fitted against the DVSI stick on the train split of the captured decode clips (2026-10-28), // by the per-frame distance over 24 mel bands: 2.31 -> 2.01 dB on test, 2.36 -> 2.10 on dev. Most of it is the formant // enhancement (weaker, split moved from 1 kHz to 460 Hz) and mixed_voiced_share 1. repeat_high_gain lifts the top band // on erased frames that repeat, as the stick does (about 3 dB per repeat at 3.5 kHz); frames around erasures 2.54 -> 2.04 // dB on test. Its own defaults are in lib/halfrate/docs/options.md; docs/tuning.md has the report. static const struct { const char *name; double value; } kHalfrateDecoderTuning[] = { {"output_gain", 2.07}, {"high_band_tilt", 0.5}, {"high_band_start", 0.66}, {"silence_gain", 0.26}, {"enhance_strength", 0.61}, {"enhance_min_weight", 0.39}, {"enhance_max_weight", 1.24}, {"enhance_power_high", 0.72}, {"enhance_power_low", 0.2}, {"enhance_split_frequency", 0.057}, {"mixed_voiced_share", 1.0}, {"repeat_high_gain", 1.78}, }; ambe2_decoder *ambe2_decoder_new_codec(int codec) { if (codec != AMBE2_CODEC_HALFRATE) return NULL; ambe2_decoder *d = (ambe2_decoder *)calloc(1, sizeof *d); if (!d) return NULL; d->hr = hr_decoder_new(); if (!d->hr) { free(d); return NULL; } for (const auto &t : kHalfrateDecoderTuning) hr_decoder_set(d->hr, t.name, t.value); return d; } ambe2_decoder *ambe2_decoder_new(void) { return ambe2_decoder_new_codec(AMBE2_CODEC_HALFRATE); } void ambe2_decoder_free(ambe2_decoder *d) { if (!d) return; hr_decoder_free(d->hr); free(d); } int ambe2_decoder_codec(const ambe2_decoder *d) { return AMBE2_CODEC_HALFRATE; } int ambe2_decoder_set_option(ambe2_decoder *d, const char *name, double value) { return d ? hr_decoder_set(d->hr, name, value) : -1; } // halfrate frame kinds -> the return values of ambe2_decode static int halfrate_kind(int kind) { switch (kind) { case HR_ERASURE: return 1; case HR_TONE: return 3; default: return 0; // HR_VOICE, HR_SILENCE } } int ambe2_decode(ambe2_decoder *d, const uint8_t *bits, int16_t *pcm) { uint8_t frame[HR_FRAME_BYTES]; bits_to_hr(bits, frame); return halfrate_kind(hr_decode(d->hr, frame, pcm)); } int ambe2_decode_packed(ambe2_decoder *d, const uint8_t *frame9, int16_t *pcm) { return halfrate_kind(hr_decode_air(d->hr, frame9, pcm)); } void ambe2_pack(const uint8_t *bits, uint8_t *frame9) { uint8_t frame[HR_FRAME_BYTES]; bits_to_hr(bits, frame); hr_fec_encode(frame, frame9); } int ambe2_unpack(const uint8_t *frame9, uint8_t *bits) { uint8_t frame[HR_FRAME_BYTES]; int errs = hr_fec_decode(frame9, frame); hr_bits_from_frame(frame, bits); return errs; } static size_t pack_all(const std::vector &bits, size_t n, uint8_t *frames) { for (size_t i = 0; i < n; i++) ambe2_pack(&bits[i * AMBE2_BITS], frames + i * AMBE2_FRAME_BYTES); return n; } size_t ambe2_stream_push_packed(ambe2_stream *s, const int16_t *pcm, size_t n, uint8_t *frames, size_t max_frames) { std::vector bits(max_frames * AMBE2_BITS + 1); return pack_all(bits, ambe2_stream_push(s, pcm, n, bits.data(), max_frames), frames); } size_t ambe2_stream_flush_packed(ambe2_stream *s, uint8_t *frames, size_t max_frames) { std::vector bits(max_frames * AMBE2_BITS + 1); return pack_all(bits, ambe2_stream_flush(s, bits.data(), max_frames), frames); }