rvoip-codec-core 0.3.8

G.711 and optional G.729/Opus/AMR audio codec implementations for RVOIP
Documentation
#!/usr/bin/env python3
"""Emit the AMR-WB ISF quantiser codebooks as Rust from TS 26.173."""
import re
import sys

tab_path, out_path = sys.argv[1], sys.argv[2]
raw = open(tab_path).read()

# (C name, Rust name, entries, vector dimension)
BOOKS = [
    ("mean_isf", "MEAN_ISF", 16, 1),
    ("dico1_isf", "DICO1", 256, 9),
    ("dico2_isf", "DICO2", 256, 7),
    ("dico21_isf", "DICO21", 64, 3),
    ("dico22_isf", "DICO22", 128, 3),
    ("dico23_isf", "DICO23", 128, 3),
    ("dico24_isf", "DICO24", 32, 3),
    ("dico25_isf", "DICO25", 32, 4),
    ("dico21_isf_36b", "DICO21_36B", 128, 5),
    ("dico22_isf_36b", "DICO22_36B", 128, 4),
    ("dico23_isf_36b", "DICO23_36B", 64, 7),
]

DOCS = {
    "MEAN_ISF": "Long-term mean ISF vector, Q15. Subtracted before quantisation\n/// and added back after, so the codebooks only ever carry deviations.",
    "DICO1": "First-stage codebook for the low nine ISFs, 256 entries of 9.",
    "DICO2": "First-stage codebook for the high seven ISFs, 256 entries of 7.",
    "DICO21": "Second-stage refinement, ISFs 0-2, 64 entries of 3.",
    "DICO22": "Second-stage refinement, ISFs 3-5, 128 entries of 3.",
    "DICO23": "Second-stage refinement, ISFs 6-8, 128 entries of 3.",
    "DICO24": "Second-stage refinement, ISFs 9-11, 32 entries of 3.",
    "DICO25": "Second-stage refinement, ISFs 12-15, 32 entries of 4.",
    "DICO21_36B": "36-bit second-stage refinement, ISFs 0-4, 128 entries of 5.",
    "DICO22_36B": "36-bit second-stage refinement, ISFs 5-8, 128 entries of 4.",
    "DICO23_36B": "36-bit second-stage refinement, ISFs 9-15, 64 entries of 7.",
}


def values(name, count):
    """Numbers inside one C array initialiser."""
    body = raw.split(f"{name}[", 1)[1].split("{", 1)[1].split("}", 1)[0]
    # Strip comments before scanning, so numbers inside them are not picked up.
    body = re.sub(r"/\*.*?\*/", "", body, flags=re.S)
    vals = [int(x) for x in re.findall(r"-?\d+", body)]
    assert len(vals) == count, f"{name}: got {len(vals)}, want {count}"
    return vals


def table(rust_name, vals, dim, per_line=8):
    doc = DOCS[rust_name]
    lines = [f"/// {doc}", f"pub const {rust_name}: [i16; {len(vals)}] = ["]
    step = dim if 1 < dim <= per_line else per_line
    for i in range(0, len(vals), step):
        lines.append("    " + ", ".join(str(v) for v in vals[i : i + step]) + ",")
    lines.append("];")
    return "\n".join(lines)


HEADER = '''//! ISF quantiser codebooks for AMR-WB, from the TS 26.173 reference.
//!
//! The quantiser is two-stage and split. The first stage codes the sixteen
//! ISFs as two vectors (nine low, seven high); the second stage refines the
//! result in narrower splits, five of them at 46 bits and three at 36. Splitting
//! is what makes the codebooks tractable: a joint 46-bit codebook over sixteen
//! dimensions would have 7e13 entries.
//!
//! Two rates because the 6.60 kbit/s mode cannot afford 46 bits for the
//! spectrum. Both share the first stage; only the refinement differs.
//!
//! These are normative constants -- the codec cannot be conformant without
//! exactly these numbers -- and are generated by `tools/gen_isf_codebooks.py`.

'''

with open(out_path, "w") as f:
    f.write(HEADER)
    parts = []
    for c_name, rust_name, count, dim in BOOKS:
        parts.append(table(rust_name, values(c_name, count * dim), dim))
    f.write("\n\n".join(parts) + "\n")

print(f"wrote {out_path}: {len(BOOKS)} codebooks")