Files
Cyrene_v2/internal/capability/transcode/amrnb/dsp_synfilt.go
T
AskaEth 4a2d276881
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(capability): 转码能力包(vendor 纯 Go AMR-NB 解码器 + 黄金向量逐样本对照 + 8 哨兵 + 注册面)
2026-10-05 13:02:28 +08:00

82 lines
3.0 KiB
Go

package amrnb
// LP synthesis and analysis filters, ported from opencore-amrnb syn_filt.cpp
// and residu.cpp (order 10). synFilt runs the all-pole synthesis 1/A(z) over a
// subframe, carrying M samples of filter memory; residu runs the all-zero
// analysis A(z). Both accumulate products in wrapping int32 (the reference's
// non-saturating MAC/MSU), so a straightforward per-output loop is bit-exact;
// synFilt's output clamp reproduces the reference's exact boundary behaviour.
// synClamp reproduces the Syn_filt overflow clamp: in-range values are an
// arithmetic >>12, out-of-range saturate to ±MAX_16. The asymmetric boundary
// (s == 0x07ffffff -> MIN_16) is a reference quirk preserved deliberately.
func synClamp(s int32) int16 {
if uint32(s+134217728) < 0x0fffffff {
return int16(s >> 12)
} else if s > 0x07ffffff {
return maxInt16
}
return minInt16
}
// synFilt computes y = x convolved with 1/A(z) over lg samples, where a[0:M+1]
// are the LP coefficients (a[0]=4096, Q12). mem[0:M] holds the M past output
// samples; if update is true it is refreshed with the last M outputs. Mirrors
// Syn_filt.
func synFilt(a, x, y []int16, lg int, mem []int16, update bool) {
var yy [cL_FRAME + cM]int16
copy(yy[:cM], mem[:cM])
af := a[:cMP1] // hoist bounds: a[0..M]
xf := x[:lg]
for i := 0; i < lg; i++ {
s := int32(0x800) + int32(af[0])*int32(xf[i])
yw := yy[i : i+cM] // yy[cM+i-j] == yw[cM-j], all in [0,cM)
s -= int32(af[1]) * int32(yw[cM-1])
s -= int32(af[2]) * int32(yw[cM-2])
s -= int32(af[3]) * int32(yw[cM-3])
s -= int32(af[4]) * int32(yw[cM-4])
s -= int32(af[5]) * int32(yw[cM-5])
s -= int32(af[6]) * int32(yw[cM-6])
s -= int32(af[7]) * int32(yw[cM-7])
s -= int32(af[8]) * int32(yw[cM-8])
s -= int32(af[9]) * int32(yw[cM-9])
s -= int32(af[10]) * int32(yw[cM-10])
out := synClamp(s)
yy[cM+i] = out
y[i] = out
}
if update {
copy(mem[:cM], y[lg-cM:lg])
}
}
// residu computes the LP residual A(z)·input over inputLen samples. buf holds
// the input with at least M samples of history before start (buf[start-M:start]
// are the past samples). Output goes to residual[0:inputLen]. Output is
// truncated (not saturated) to 16 bits, matching the reference. Mirrors Residu.
func residu(coef, buf []int16, start, inputLen int, residual []int16) {
// res[i] = (0x800 + sum_{j=0..M} coef[j]·buf[start+i-j]) >> 12. Reversing the
// coefficients turns the inner sum into a fixed-window FIR over buf[start-M:]
// (res[i] = sum_k arev[k]·buf[start-M+i+k]), so it vectorises via firRaw. The
// reference accumulates in plain wrapping int32, so the lane-parallel order is
// bit-identical, and adding the 0x800 rounding bias after the sum is exact
// (integer addition is associative mod 2^32).
var arev [cMP1]int16
for k := 0; k <= cM; k++ {
arev[k] = coef[cM-k]
}
var dstArr [cL_SUBFR]int32
var dst []int32
if inputLen <= cL_SUBFR {
dst = dstArr[:inputLen]
} else {
dst = make([]int32, inputLen)
}
firRaw(dst, buf[start-cM:], arev[:])
for i := 0; i < inputLen; i++ {
residual[i] = int16((0x800 + dst[i]) >> 12)
}
}