Initial public release of JustOneCacophony

This commit is contained in:
2026-09-01 16:31:38 +08:00
commit aa3a519f79
38 changed files with 9073 additions and 0 deletions
+216
View File
@@ -0,0 +1,216 @@
"""OAMD 位载荷 → 16 个对象槽的 q1/q2/q3 增量状态。
槽 0 是 bed/LFE;槽 1..15 对应输出 ch1..15 的对象元数据。
"""
import numpy as np
from variant_error import UnsupportedVariantError, bytes_descriptor
Q1_OFF = 192
N_Q12 = 62
N_Q3 = 15
SAMPLE_OFFSET_INDEX = (8, 16, 18, 24)
RAMP_DURATIONS = (0, 512, 1536)
RAMP_DURATION_INDEX = (
32, 64, 128, 256, 320, 480, 1000, 1001,
1024, 1600, 1601, 1602, 1920, 2000, 2002, 2048,
)
def q_of(k, n):
q = int(np.floor(32768.0 * k / n + 0.5))
return min(32767, q)
def _payload_bits(bits_one):
if isinstance(bits_one, (bytes, bytearray, memoryview)):
src = np.frombuffer(bits_one, dtype=np.uint8)
else:
src = np.asarray(bits_one, dtype=np.uint8)
if src.ndim == 1 and src.shape[0] in (536, 552):
bits = src
elif src.size in (67, 69):
bits = np.unpackbits(src.reshape(-1), bitorder="big")
else:
raw = (bytes(bits_one) if isinstance(bits_one, (bytes, bytearray, memoryview))
else np.asarray(bits_one, dtype=np.uint8).tobytes())
raise UnsupportedVariantError(
"oamd", f"payload_length_{len(raw)}B",
f"发现未覆盖的 OAMD 载荷长度 {len(raw)}B",
details={
"supported_payload_bytes": [67, 69],
"payload": bytes_descriptor(raw),
"repair_hint": "检查 OAMD header、element 数量及可选字段造成的位偏移变化",
})
raw_payload = np.packbits(bits, bitorder="big").tobytes()
return bits, raw_payload
class _BitReader:
def __init__(self, bits):
self.bits = bits
self.position = 0
def read(self, count):
end = self.position + count
if end > len(self.bits):
raise ValueError(f"OAMD 位流越界: bit={self.position}, need={count}")
value = 0
for bit in self.bits[self.position:end]:
value = (value << 1) | int(bit)
self.position = end
return value
def skip(self, count):
self.read(count)
def _variable_bits(reader, width, max_groups=5):
value = 0
for _ in range(max_groups + 1):
value += reader.read(width)
if not reader.read(1):
return value
value = (value + 1) << width
raise ValueError(f"OAMD variable_bits({width}) 延伸组过多")
def _update_timing(bits, alternate_object_present, element_count, raw_payload):
"""按 OAMD element/MDUpdateInfo 读取位置块的开始偏移和 ramp 时长。"""
reader = _BitReader(bits)
reader.position = 14
for _ in range(element_count):
element_index = reader.read(4)
element_length = _variable_bits(reader, 4)
element_end = reader.position + element_length + 1
reader.skip(5 if alternate_object_present else 1)
if element_index == 1:
offset_code = reader.read(2)
if offset_code == 0:
sample_offset = 0
elif offset_code == 1:
sample_offset = SAMPLE_OFFSET_INDEX[reader.read(2)]
elif offset_code == 2:
sample_offset = reader.read(5)
else:
raise UnsupportedVariantError(
"oamd", "md_sample_offset_mode",
"OAMD 使用了当前未覆盖的 MD sample-offset 模式",
details={"payload": bytes_descriptor(raw_payload)})
block_count = reader.read(3) + 1
blocks = []
for _block in range(block_count):
block_offset_factor = reader.read(6)
block_offset = sample_offset + block_offset_factor * 32
ramp_code = reader.read(2)
if ramp_code == 3:
if reader.read(1):
ramp_duration = RAMP_DURATION_INDEX[reader.read(4)]
else:
ramp_duration = reader.read(11)
else:
ramp_duration = RAMP_DURATIONS[ramp_code]
blocks.append((block_offset, ramp_duration))
if len(blocks) != 1:
raise UnsupportedVariantError(
"oamd", "multiple_position_blocks",
"OAMD 一帧含多个对象位置更新块,固定位置窗口不能安全套用",
details={
"block_count": len(blocks),
"blocks": blocks,
"payload": bytes_descriptor(raw_payload),
"repair_hint": "按 ObjectInfoBlock 顺序逐块解析坐标,再生成分段 ADM ramp",
})
return blocks[0]
reader.position = element_end
raise UnsupportedVariantError(
"oamd", "missing_object_element",
"OAMD 中没有 object element (element_index=1)",
details={"payload": bytes_descriptor(raw_payload)})
def frame_update(bits_one):
"""单帧 OAMD → 位置字段增量及其 sample offset/ramp duration。"""
bits, raw_payload = _payload_bits(bits_one)
header = {
"version": int((bits[0] << 1) | bits[1]),
"objects_minus_one": int(sum(int(bits[2 + i]) << (4 - i) for i in range(5))),
"dynamic_object_only": int(bits[7]),
"lfe_present": int(bits[8]),
"alternate_object_present": int(bits[9]),
"element_count": int(sum(int(bits[10 + i]) << (3 - i) for i in range(4))),
}
if raw_payload[:2] != b"\x1f\x88":
raise UnsupportedVariantError(
"oamd", f"header_signature_{raw_payload[:2].hex()}",
"OAMD 长度已知,但 header 与当前位置字段布局不一致",
details={
"supported_header_prefix_hex": "1f88",
"header_probe": header,
"payload": bytes_descriptor(raw_payload),
"repair_hint": "按新 header 的 program assignment 和 element 布局重新定位对象位置字段",
})
block_offset, ramp_duration = _update_timing(
bits, bool(header["alternate_object_present"]), header["element_count"], raw_payload)
weights = 1 << np.arange(7, -1, -1)
out = {}
for obj in range(16):
start = 112 + 31 * (obj - 3)
wq1 = int((bits[start:start + 8] * weights).sum())
wq2 = int((bits[start + 8:start + 16] * weights).sum())
wq3 = int((bits[start + 16:start + 24] * weights).sum())
if obj and (wq1 >> 6 != 3 or wq2 & 2 != 2 or wq3 & 0x1F != 1):
raise UnsupportedVariantError(
"oamd", "position_layout_signature",
"OAMD 长度和 header 已知,但对象位置字段标记或位偏移发生变化",
details={
"object_slot": obj,
"position_start_bit": start,
"q1_window_hex": f"{wq1:02x}",
"q2_window_hex": f"{wq2:02x}",
"q3_window_hex": f"{wq3:02x}",
"header_probe": header,
"payload": bytes_descriptor(raw_payload),
"repair_hint": "解析 OAMD element 可选字段并更新每个对象的位置窗口偏移",
})
k1 = wq1 - Q1_OFF
if obj == 0:
out[(0, "q1")] = None
out[(0, "q2")] = None
else:
out[(obj, "q1")] = q_of(k1, N_Q12) if 0 <= k1 <= N_Q12 else None
k2 = wq2 >> 2
if obj != 0:
out[(obj, "q2")] = q_of(k2, N_Q12) if 0 <= k2 <= N_Q12 else None
k3 = (wq2 & 1) * 8 + (wq3 >> 5)
out[(obj, "q3")] = q_of(k3, N_Q3) if 0 <= k3 <= N_Q3 else None
return {
"values": out,
"block_offset_samples": block_offset,
"ramp_duration_samples": ramp_duration,
}
def frame_update_values(bits_one):
"""兼容接口:只返回 ``{(slot, field): q|None}``。"""
return frame_update(bits_one)["values"]
class JocFieldState:
"""未更新/非法窗保持旧值;slot0 q1/q2 的 DLL 初值为中心 16384。"""
def __init__(self):
self.q = {(obj, field): 0 for obj in range(16)
for field in ("q1", "q2", "q3")}
self.q[(0, "q1")] = 16384
self.q[(0, "q2")] = 16384
def apply(self, updates):
for key, value in updates.items():
if value is not None:
self.q[key] = value
return self
def snapshot(self):
return dict(self.q)