// celt_partition.mbt
//
// band 形状的递归分割解码(RFC 6716 §4.3.4.4 Split Decoding)。
//
// 对应参考实现 celt_bands.c 的 quant_partition 解码侧:位数超过最大
// 码本 1.5 bit 且 N>2 就把带切成两半,先按 celt_compute_theta 解出的
// θ 把位数分给 mid/side(含 rebalance 补偿),递归到叶;叶上用
// celt_bits2pulses 把位数换成脉冲索引,在预算内回退,再交给 PVQ
// 解出脉冲向量。
//
// 递归同时完成叶上的谱应用(对应 alg_unquant 的解码侧与 q==0 填充),
// 由谱上下文 CeltBandCtx 控制:
//
// - active=false:只走控制与熵解码(本文件的 celt_decode_quant_partition
// 包装入口),结果仍是 splits/leaves/remaining;
// - active=true:额外把叶写进 x——q>0 时按 gain/√Σy² 归一化并做
// 展宽旋转,q==0 时按 fill/折叠源填充噪声并重归一化,同时消费
// lcg seed、累积每带的 collapse mask(cm 按 side<<(B>>1) 归并)。
//
// 谱的频序还原(tf_inverse)、fill/cm 的位图变换与 n==1 特例在
// celt_quant_band.mbt 的带级入口完成。
///|
/// 谱上下文:数组字段按引用共享;`seed`/`cm` 是单元素出参盒。
/// `active=false` 时所有谱写入与 seed 消费都被跳过。
priv struct CeltBandCtx {
active : Bool
// 本带频谱(长度 n,叶按偏移就地写)
x : Array[Double]
// 折叠源(空数组 = 无折叠,q==0 走噪声路径)
lowband : Array[Double]
has_lowband : Bool
// 展宽旋转的档位(§4.3.4.3 的 spread 决策)
spread : Int
// lcg seed 出参(初值由调用方放入)
seed : Array[Int64]
}
///|
/// 一个叶节点的解码控制量(前序排列于 CeltPartitionResult.leaves)。
pub struct CeltPartitionLeaf {
/// 叶的维度 N
n : Int
/// 该叶的时间块数 B(随分割折半、随升频翻倍后的值)
blocks : Int
/// 该叶的 LM(分割到顶为 -1)
lm : Int
/// 进入叶时的可用位数(1/8 bit)
b : Int
/// bits2pulses 的结果(经预算循环回退后的伪脉冲索引)
q : Int
/// 实际占用位数 pulses2bits(q)(预算扣减量)
used : Int
/// 叶上的 fill 位图(经过带级与分割级变换后的最终值)
fill : Int
/// PVQ 解出的脉冲向量;q==0 时为空数组(叶上无熵消费)
pulses : Array[Int]
}
///|
/// 整棵树的解码结果:split 节点与叶都按熵消费顺序(前序)排列。
pub struct CeltPartitionResult {
/// 每个分割节点的 θ 控制量
splits : Array[CeltThetaSplit]
/// 叶序列
leaves : Array[CeltPartitionLeaf]
/// 结束时的剩余预算(初始值扣掉全部熵消费)
remaining_bits : Int
}
///|
/// quant_partition 的解码入口(控制与熵解码路径,谱写入关闭)。
///
/// `band` 是原始带号(查脉冲缓存用,不随分割改变),`lm` 是本带
/// 帧长索引,`n` 是带的 MDCT bin 数,`b` 是本带可用位数(1/8 bit),
/// `blocks` 是时间块数 B,`fill` 是噪声填充位图,`remaining_bits` 是
/// 进入本带时的全局剩余预算(1/8 bit)。
pub fn celt_decode_quant_partition(
dec : RangeDecoder,
band : Int,
lm : Int,
n : Int,
b : Int,
blocks : Int,
fill : Int,
remaining_bits : Int,
) -> CeltPartitionResult {
let splits : Array[CeltThetaSplit] = []
let leaves : Array[CeltPartitionLeaf] = []
let ctx = CeltBandCtx::{
active: false,
x: [],
lowband: [],
has_lowband: false,
spread: 0,
seed: [0L],
}
let (remaining, _) = celt_partition_rec(
dec, band, lm, n, b, blocks, fill, remaining_bits, splits, leaves, ctx, 0, 0,
1.0,
)
{ splits, leaves, remaining_bits: remaining, }
}
///|
/// 噪声填充的一个样本:seed 按 int32 重解释后算术右移 20 位。float
/// 构建的 SHL32(…, NORM_SHIFT-14) 是恒等宏,故不再二次缩放。
fn celt_fill_sample(seed : Int64) -> Double {
let sv = seed & 0xFFFFFFFFL
let iv = if sv >= 0x80000000L { sv - 0x100000000L } else { sv }
(iv >> 20).to_double()
}
///|
/// 叶向量的塌缩位图(celt_vq.c 的 extract_collapse_mask):B≤1 恒 1,
/// 否则按 N/B 分块,块内任一系数非零则该位为 1。
fn celt_extract_collapse_mask(y : Array[Int], n : Int, blocks : Int) -> Int {
if blocks <= 1 {
return 1
}
let n0 = celt_udiv(n, blocks)
let mut mask = 0
for i in 0.. Unit {
if spread == 0 || 2 * k >= n {
return
}
let sub = Array::make(n, 0.0)
for j in 0.. (Int, Int) {
let blocks0 = blocks
let off = cel_cache_index50[(lm + 1) * CEL_NB_EBANDS + band]
let kmax = cel_cache_bits50[off].to_int()
// 位数超过最大码本 1.5 bit 才分割;N≤2 或 LM 到顶(-1)时必须落叶
if lm != -1 && b > cel_cache_bits50[off + kmax].to_int() + 12 && n > 2 {
let n2 = n >> 1
let mut fill_mid = fill
if blocks == 1 {
fill_mid = (fill & 1) | (fill << 1)
}
let blocks_half = (blocks + 1) >> 1
let (sctx, b_mid, fill_theta) = celt_compute_theta(
dec,
band,
lm - 1,
n2,
b,
blocks_half,
blocks0,
false,
0,
remaining,
false,
fill_mid,
)
let mut remaining_after = remaining - sctx.qalloc
// 低能量 MDCT 多分点位数:B0>1 且 θ 不在 0/满量程时微调 delta
let mut delta = sctx.delta
let itheta = sctx.itheta
if blocks0 > 1 && (itheta & 0x3FFF) != 0 {
if itheta > 8192 {
// 近似预回声掩蔽
delta = delta - (delta >> (4 - (lm - 1)))
} else {
// 前向掩蔽 1.5 dB/10ms 的斜率,上限 0
let t = delta + (n2 << CEL_BITRES >> (5 - (lm - 1)))
delta = if t < 0 { t } else { 0 }
}
}
let mut mbits = celt_sudiv(b_mid - delta, 2)
if mbits > b_mid {
mbits = b_mid
}
if mbits < 0 {
mbits = 0
}
let sbits = b_mid - mbits
splits.push(sctx)
let rebalance0 = remaining_after
// mid/side 的缩放增益:float 路径下 mid = imid/32768
let gain_mid = gain * (sctx.imid.to_double() / 32768.0)
let gain_side = gain * (sctx.iside.to_double() / 32768.0)
let mut cm_tree = 0
if mbits >= sbits {
let (r1, cm_mid) = celt_partition_rec(
dec,
band,
lm - 1,
n2,
mbits,
blocks_half,
fill_theta,
remaining_after,
splits,
leaves,
ctx,
x_off,
lb_off,
gain_mid,
)
remaining_after = r1
let rebalance = mbits - (rebalance0 - remaining_after)
let mut sbits_r = sbits
if rebalance > 3 << CEL_BITRES && itheta != 0 {
sbits_r += rebalance - (3 << CEL_BITRES)
}
let (r2, cm_side) = celt_partition_rec(
dec,
band,
lm - 1,
n2,
sbits_r,
blocks_half,
fill_theta >> blocks_half,
remaining_after,
splits,
leaves,
ctx,
x_off + n2,
lb_off + n2,
gain_side,
)
remaining_after = r2
cm_tree = cm_mid | (cm_side << (blocks >> 1))
} else {
let (r1, cm_side) = celt_partition_rec(
dec,
band,
lm - 1,
n2,
sbits,
blocks_half,
fill_theta >> blocks_half,
remaining_after,
splits,
leaves,
ctx,
x_off + n2,
lb_off + n2,
gain_side,
)
remaining_after = r1
let rebalance = sbits - (rebalance0 - remaining_after)
let mut mbits_r = mbits
if rebalance > 3 << CEL_BITRES && itheta != 16384 {
mbits_r += rebalance - (3 << CEL_BITRES)
}
let (r2, cm_mid) = celt_partition_rec(
dec,
band,
lm - 1,
n2,
mbits_r,
blocks_half,
fill_theta,
remaining_after,
splits,
leaves,
ctx,
x_off,
lb_off,
gain_mid,
)
remaining_after = r2
cm_tree = cm_mid | (cm_side << (blocks >> 1))
}
return (remaining_after, cm_tree)
}
// ---- 叶:bits2pulses + 预算循环 ----
let mut q = celt_bits2pulses(band, lm, b)
let mut used = celt_pulses2bits(band, lm, q)
let mut remaining_leaf = remaining - used
while remaining_leaf < 0 && q > 0 {
remaining_leaf += used
q -= 1
used = celt_pulses2bits(band, lm, q)
remaining_leaf -= used
}
let mut pulses : Array[Int] = []
let mut leaf_cm = 0
if q != 0 {
pulses = celt_decode_pulses(dec, n, celt_get_pulses(q))
leaf_cm = celt_extract_collapse_mask(pulses, n, blocks)
if ctx.active {
let k = celt_get_pulses(q)
let mut yy = 0.0
for y in pulses {
let yd = y.to_double()
yy += yd * yd
}
let g = gain / @math.pow(yy, 0.5)
for j in 0..>20 的伪随机样值
leaf_cm = mask
if ctx.active {
for j in 0..