82 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|