"""Engine-internal guards (zero latency, no user configuration, on by default). Port of the guards used by the offline reference decoder (DurationPrior, loop_detect, and the FastBatch guard logic), with the loop detector vectorised over seats (same decision per seat as guards.loop_detect; maya3/tests/test_guards.py). 1. duration cap : stop at 2.2 x expected + 1.2 s (digit/pause/language-aware prior), floor 0.5 x old-prior + 8 frames 2. loop guard : every 2 frames from frame 32, on the cb0 tail (last 96 frames): a 32-frame stall or a periodic pattern (c[t] == c[t-p] on >= 70 % of the last 64 frames, p = 2..16) -> EOS boost (+4 on the AEOS logit; the model ends the phrase itself), removed when the loop clears; a loop that persists 24 frames after the boost is stopped (loop_backstop). Tag-aware: suspended for [-0.5 s, +3 s] around each inline [tag]'s estimated time. """ from __future__ import annotations import dataclasses import json import os import re import numpy as np HERE = os.path.dirname(os.path.abspath(__file__)) PRIOR_PATH = os.environ.get("MAYA3_DUR_PRIOR", os.path.join(HERE, "assets", "duration_prior.json")) PAUSE = re.compile(r"[,;:.!?।॥…\-]") TAG = re.compile(r"\[[a-z ]+\]") FRAME_S = 0.08 def text_feats(t): letters = sum(1 for ch in t if ch.isalpha() or (0x900 <= ord(ch) <= 0xDFF)) latin = sum(1 for ch in t if ch.isascii() and ch.isalpha()) return np.array([1.0, letters - latin, latin, len(PAUSE.findall(t)), sum(ch.isdigit() for ch in t)]) class DurationPrior: def __init__(self, path=PRIOR_PATH): d = json.load(open(path)) self.coef = {l: np.array(v["coef"]) for l, v in d["langs"].items()} self.default = np.mean(list(self.coef.values()), 0) def expected_s(self, text, lang): return float(max(0.35, text_feats(text) @ self.coef.get(lang, self.default))) def cap_frames(self, text, lang, k=2.2, margin_s=1.2): return int((k * self.expected_s(text, lang) + margin_s) / FRAME_S) @dataclasses.dataclass class GuardConfig: enabled: bool = True dur_k: float = 2.2 dur_margin_s: float = 1.2 loop: bool = True stall: int = 32 period_win: int = 64 period_thr: float = 0.7 pmin: int = 2 pmax: int = 16 start: int = 32 # first frame the loop rule is evaluated every: int = 2 mode: str = "boost" # boost | stop boost: float = 4.0 backstop: int = 24 tag_aware: bool = True tag_hold_s: float = 3.0 hist: int = 96 # cb0 tail kept per seat # loop guard v2b windows (suspend_windows_v2): ON by default. Measured against the v1 windows on an eval subset with text # normalisation: real_fail_cq 1.16 -> 0.96 %, no-stop 0 -> 0, emotion CER 7.5 -> 5.6 %. MAYA3_GUARD_V2=0 restores v1 tag windows aware_v2: bool = os.environ.get("MAYA3_GUARD_V2", "1") == "1" def tag_windows(text, lang, prior, hold_s=3.0): out = [] for m in TAG.finditer(text): t = prior.expected_s(text[:m.start()], lang) if m.start() > 0 else 0.0 out.append((max(0, int((t - 0.5) / FRAME_S)), int((t + hold_s) / FRAME_S))) return tuple(out) def loop_detect_batch(W, n, cfg: GuardConfig): """W: [S, hist] int array, row i = the last `hist` cb0 codes of seat i, right-aligned (newest last; -1 = empty); n: [S] frames generated so far. Returns list of None | 'stall' | 'period:

' (guards.loop_detect semantics with min_frames=12 on the last 96 codes).""" S = W.shape[0] out = [None] * S if S == 0: return out st = cfg.stall tail = W[:, -st:] stall = (n >= max(12, st)) & (tail == tail[:, :1]).all(1) pw = cfg.period_win win = W[:, -pw:] can_p = n >= max(12, st, pw) hit_p = np.full(S, 0, np.int64) if can_p.any(): for p in range(cfg.pmin, cfg.pmax + 1): frac = (win[:, p:] == win[:, :-p]).mean(1) new = (hit_p == 0) & can_p & (frac >= cfg.period_thr) hit_p[new] = p for i in range(S): if n[i] < max(12, st): continue if stall[i]: out[i] = "stall" elif hit_p[i]: out[i] = f"period:{int(hit_p[i])}" return out # ---- loop guard v2 (derived from the remaining eval failures; identical in the reference decoder's guards) ---------- # The v1 tag windows ([-0.5 s, +3 s] around the prior-estimated tag time) miss most backstop cuts: style tags colour all the # speech after them (whispered / sad / excited speech trips the period rule seconds later), the real tag time drifts from the # prior (cuts happen at 1.0-2.1 x the piece's expected frames), and fillers / dialogue speaker switches are legitimate pauses. # v2 windows (no loop action inside; the duration cap still applies): # style tag [0.8 t - 0.5 s, 1.5 t + 4.0 s] (style_hold_s=None: to the end of the piece; rejected, see below) # event tag [0.8 t - 0.5 s, 1.5 t + 4.0 s] # filler [0.8 t - 0.5 s, 1.5 t + 2.5 s] hmm / umm / uh / ಹ್ಮ್ / उम्म / ഉം ... # speaker [0.8 t - 0.5 s, 1.5 t + 2.0 s] "Name: " after a sentence end (dialogue speaker switch) STYLE_TAGS = {"whispers", "whispering", "sad", "excited", "curious", "shouting", "angry", "frustrated", "sarcastic", "crying", "nervous", "scared", "happy", "panicked", "amazed", "worried", "calm", "serious", "surprised", "cheerful", "tired", "fearful", "disgusted", "confused", "sleepy", "annoyed", "softly", "loudly"} FILLER_RE = re.compile(r"(? 0 else 0.0 a = max(0, int((0.8 * t - 0.5) / FRAME_S)) b = end_frames if hold is None else int((1.5 * t + hold) / FRAME_S) out.append((a, b)) return tuple(out)