109 lines
3.3 KiB
Go
109 lines
3.3 KiB
Go
package amrnb
|
||
|
||
// Adaptive formant post-filter, ported from opencore-amrnb pstfilt.cpp. For each
|
||
// subframe it builds the weighted LP filters A(z/γ3) and A(z/γ4), forms the
|
||
// residual through A(z/γ3), derives a spectral-tilt compensation coefficient
|
||
// from the truncated impulse response, applies it via preemphasis, synthesizes
|
||
// through 1/A(z/γ4), and finally matches the energy back to the synthesis with
|
||
// AGC. Uses the building blocks weightAi/residu/synFilt/preemphasis/agc; this is
|
||
// the orchestration that chains them. Bit-exact with the reference.
|
||
|
||
const (
|
||
cL_H = 22 // truncated impulse-response length
|
||
cMU = 26214 // tilt compensation factor (0.8)
|
||
)
|
||
|
||
// γ bandwidth-expansion factors (pstfilt.cpp).
|
||
var gamma3MR122 = [cM]int16{22938, 16057, 11240, 7868, 5508, 3856, 2699, 1889, 1322, 925}
|
||
var gamma3 = [cM]int16{18022, 9912, 5451, 2998, 1649, 907, 499, 274, 151, 83}
|
||
var gamma4MR122 = [cM]int16{24576, 18432, 13824, 10368, 7776, 5832, 4374, 3281, 2461, 1846}
|
||
var gamma4 = [cM]int16{22938, 16057, 11240, 7868, 5508, 3856, 2699, 1889, 1322, 925}
|
||
|
||
// postFilterState holds the formant post-filter memory across frames.
|
||
type postFilterState struct {
|
||
res2 [cL_SUBFR]int16
|
||
memSynPst [cM]int16
|
||
preemph int16
|
||
agc agcState
|
||
synthBuf [cM + cL_FRAME]int16 // M history + current frame
|
||
}
|
||
|
||
func (st *postFilterState) reset() {
|
||
*st = postFilterState{}
|
||
}
|
||
|
||
// postFilter post-filters one 160-sample synthesis frame in place. az4 holds the
|
||
// four per-subframe LP coefficient sets (4*cMP1). Mirrors Post_Filter.
|
||
func (st *postFilterState) postFilter(mode int, syn []int16, az4 []int16) {
|
||
var ap3, ap4 [cMP1]int16
|
||
var h [cL_H]int16
|
||
|
||
synWork := st.synthBuf[cM:] // syn_work[i] == synthBuf[M+i]
|
||
copy(synWork[:cL_FRAME], syn[:cL_FRAME])
|
||
|
||
azOff := 0
|
||
for iSubfr := 0; iSubfr < cL_FRAME; iSubfr += cL_SUBFR {
|
||
az := az4[azOff:]
|
||
if mode == dMR122 || mode == dMR102 {
|
||
weightAi(az, gamma3MR122[:], ap3[:])
|
||
weightAi(az, gamma4MR122[:], ap4[:])
|
||
} else {
|
||
weightAi(az, gamma3[:], ap3[:])
|
||
weightAi(az, gamma4[:], ap4[:])
|
||
}
|
||
|
||
// Residual through A(z/γ3); syn_work[iSubfr] == synthBuf[M+iSubfr].
|
||
residu(ap3[:], st.synthBuf[:], cM+iSubfr, cL_SUBFR, st.res2[:])
|
||
|
||
// Truncated impulse response of A(z/γ3)/A(z/γ4).
|
||
copy(h[:cMP1], ap3[:cMP1])
|
||
for i := cMP1; i < cL_H; i++ {
|
||
h[i] = 0
|
||
}
|
||
synFilt(ap4[:], h[:], h[:], cL_H, h[cMP1:], false)
|
||
|
||
// Tilt compensation coefficient from the impulse-response autocorr.
|
||
var lTmp int32
|
||
for i := cL_H - 1; i >= 0; i-- {
|
||
t2 := int32(h[i]) * int32(h[i])
|
||
if t2 == 0x40000000 {
|
||
break // reference sets overflow and stops without adding
|
||
}
|
||
lTmp = L_add(lTmp, t2<<1)
|
||
}
|
||
temp1 := int16(lTmp >> 16)
|
||
|
||
lTmp = 0
|
||
for i := cL_H - 2; i >= 0; i-- {
|
||
t2 := int32(h[i]) * int32(h[i+1])
|
||
if t2 == 0x40000000 {
|
||
break
|
||
}
|
||
lTmp = L_add(lTmp, t2<<1)
|
||
}
|
||
temp2 := int16(lTmp >> 16)
|
||
|
||
if temp2 <= 0 {
|
||
temp2 = 0
|
||
} else {
|
||
lt := (int32(temp2) * cMU) >> 15
|
||
if lt&0x00010000 != 0 {
|
||
lt |= int32(-0x10000)
|
||
}
|
||
temp2 = int16(lt)
|
||
temp2 = div_s(temp2, temp1)
|
||
}
|
||
|
||
preemphasis(&st.preemph, st.res2[:], temp2, cL_SUBFR)
|
||
|
||
synFilt(ap4[:], st.res2[:], syn[iSubfr:], cL_SUBFR, st.memSynPst[:], true)
|
||
|
||
st.agc.agc(synWork[iSubfr:], syn[iSubfr:], cAGC_FAC, cL_SUBFR)
|
||
|
||
azOff += cMP1
|
||
}
|
||
|
||
// Carry the last M samples as history for the next frame.
|
||
copy(st.synthBuf[:cM], synWork[cL_FRAME-cM:cL_FRAME])
|
||
}
|