// celt_synthesis.mbt
//
// 帧级时域合成链(RFC 6716 §4.3.7 合成 + §4.3.7.1 后滤波 + §4.3.7.2
// 去重 emphasis):把 celt_decode_frame 的 freq 输出接成 PCM。顺序与参考
// 实现 celt_celt_decoder.c 的 celt_decode_frame 逐段对齐(浮点构建路径):
//
// - 缓冲衔接:参考实现每帧开头 OPUS_MOVE 左移(1508 行),out_syn 固定
// 落在 decode_mem 的 2048−N 处——其前是连续的 comb 后历史,其头部
// [0, ov/2) 承接上一帧 raw 尾。这里等价拆成两个独立缓冲:pending
// (ov/2 = 60,交由 IMDCT 衔接)与 hist(2048−960 = 1088,comb 前
// 历史;comb 最远回读 T(≤1022)+2 = 1024,1088 恒覆盖且与参考实现
// 最小历史区同宽);
// - IMDCT(celt_imdct.mbt,含 TDAC 镜像与瞬态分块);
// - 两段 comb(1565–1571 行):首段 [0, 120) 用 旧 → st-> 参数,LM=0
// 时即整帧;LM≠0 时第二段 [120, N) 用 st-> → 本帧帧头局部参数,过渡
// 窗即 comb 自带的 overlap 窗。周期在调用前双双钳到 ≥15(1563–1564
// 行,就地写回——1574 行轮换搬的是钳后值);
// - 参数轮换(1574–1579 行);LM≠0 再把 old 同步成本帧参数(1582–1584
// 行)。由此:LM≠0 的本帧参数过渡发生在本帧 [120, 240);LM=0 的本帧
// 参数要到下一帧才开始过渡(整帧首段即过渡窗);
// - 去重 emphasis(1623 行):一阶 IIR,状态 st->preemph_memD 跨帧;
// 实现复用 celt_mode.mbt 的 celt_deemphasis(float 构建下 C 端
// VERY_SMALL = 1e-30 被舍入吸收,不实现,与既有单测口径一致)。
//
// 参考实现 comb 只读不写历史尾,IMDCT 输出的 SATURATE 在浮点构建下是
// 恒等宏——两者均已省略。prefilter_and_fold 只在丢包路径置位(1101 行),
// 正常帧 1630 行清零,本流程无丢包输入,不实现。
///|
/// comb 前历史长度:参考实现 decode_buffer_size(2048) − 最大帧长(960)。
const CEL_SYNTH_HIST : Int = 1088
///|
/// 时域合成状态(跨帧,全零初始化 = 参考实现的 memset 清零)。
pub struct CeltSynthState {
/// 上一帧 raw 尾部 ov/2 个样本(§4.3.7 TDAC 衔接)
mut pending : Array[Double]
/// 连续的 comb 后历史(长度 CEL_SYNTH_HIST,只在尾部滚动)
hist : Array[Double]
/// st->postfilter_period_old
mut pf_period_old : Int
/// st->postfilter_gain_old
mut pf_gain_old : Double
/// st->postfilter_tapset_old
mut pf_tapset_old : Int
/// st->postfilter_period(本帧首段的「新」侧)
mut pf_period : Int
/// st->postfilter_gain
mut pf_gain : Double
/// st->postfilter_tapset
mut pf_tapset : Int
/// st->preemph_memD[0]
mut deemph_mem : Double
}
///|
/// 全零初始状态。
pub fn CeltSynthState::new() -> CeltSynthState {
CeltSynthState::{
pending: Array::make(CEL_OVERLAP >> 1, 0.0),
hist: Array::make(CEL_SYNTH_HIST, 0.0),
pf_period_old: 0,
pf_gain_old: 0.0,
pf_tapset_old: 0,
pf_period: 0,
pf_gain: 0.0,
pf_tapset: 0,
deemph_mem: 0.0,
}
}
///|
/// 一帧频谱 → PCM:IMDCT 衔接 → 两段 comb → 参数轮换 → 去重 emphasis。
///
/// `pf_pitch/pf_gain/pf_tapset` 是本帧帧头解出的后滤波参数(未启用时为
/// 0/0.0/0,与参考实现的局部变量一致)。`state` 跨帧就地更新,返回本帧
/// `120 << lm` 个样本。
pub fn celt_synth_frame(
freq : Array[Double],
lm : Int,
is_transient : Bool,
pf_pitch : Int,
pf_gain : Double,
pf_tapset : Int,
state : CeltSynthState,
) -> Array[Double] {
let n = CEL_SHORT_MDCT << lm
// IMDCT:pending 承接上一帧 raw 尾,得到本帧输出与新尾
let (out, new_pending) = celt_imdct_frame(
freq,
lm,
is_transient,
state.pending,
)
// 工作缓冲 = 历史 + 本帧输出;comb 就地改写本帧段,历史段只读
let h = state.hist.length()
let work = Array::make(h + n, 0.0)
for i in 0.. 参数(LM=0 时即整帧)
celt_comb_filter(
work,
h,
state.pf_period_old,
state.pf_period,
CEL_SHORT_MDCT,
state.pf_gain_old,
state.pf_gain,
state.pf_tapset_old,
state.pf_tapset,
win,
)
if lm != 0 {
// 第二段:[120, N) st-> → 本帧帧头参数
celt_comb_filter(
work,
h + CEL_SHORT_MDCT,
state.pf_period,
pf_pitch,
n - CEL_SHORT_MDCT,
state.pf_gain,
pf_gain,
state.pf_tapset,
pf_tapset,
win,
)
}
// 参数轮换(1574–1579 行)
state.pf_period_old = state.pf_period
state.pf_gain_old = state.pf_gain
state.pf_tapset_old = state.pf_tapset
state.pf_period = pf_pitch
state.pf_gain = pf_gain
state.pf_tapset = pf_tapset
if lm != 0 {
// LM≠0:old 同步成本帧参数(1582–1584 行)
state.pf_period_old = state.pf_period
state.pf_gain_old = state.pf_gain
state.pf_tapset_old = state.pf_tapset
}
// 去重 emphasis:输入 = comb 后的本帧段,状态跨帧
let x = Array::make(n, 0.0)
for j in 0.. CeltAudioResult {
let r = celt_decode_frame(dec, frame_bytes, start, end, lm, celt)
let pcm = celt_synth_frame(
r.freq,
lm,
r.header.is_transient,
r.header.postfilter_pitch,
r.header.postfilter_gain,
r.header.postfilter_tapset,
synth,
)
CeltAudioResult::{ header: r.header, pcm, }
}