
    8jl                        S r SSKJr  SSKJrJrJr  SSKrSSK	J
r
  SrSrSrS	rSS
 jrSS jrSS jrSS jr S   SS jjr S   SS jjr  S         SS jjrg)a%  Brow + eyeSquint tremor baked into training targets.

Direct Python port of the runtime tremor in
`tools/blendshape-player.html` so dataset `.npz` targets contain the same
micro-motion that we've been judging in the viewer.

Channels:
  - 0, 1: browDownLeft/Right       (silence-gated)
  - 2:    browInnerUp              (silence-gated)
  - 3, 4: browOuterUpLeft/Right    (silence-gated)
  - 18, 19: eyeSquintLeft/Right    (always-on)

Bilateral pairs share a single noise stream so L/R move together
(orbicularis oculi tone, brow micro-furrowing).
    )annotations)ListOptionalTupleN)gaussian_filter1d)r         )      )      c                D    SnU  H  nU[        U5      -  S-  S-  nM     U$ )zFNV-1a-ish 32-bit hash. Mirrors player's `_hashStr` so identical
scenario IDs produce identical noise streams in Python and JS.l   9 i     )ord)shcs      [/home/ubuntu/service/kemix-engine/package/face/animasync-face-v3/scripts/compiler/tremor.py	_hash_strr      s/     	A#a&jH$
2 H    c                "   ^ U S-  mSU4S jjnU$ )zADeterministic uniform-[0,1) PRNG matching player's `_mulberry32`.r   c                    > TS-   S-  mTn X S-	  -  SU -  -  S-  n X U S-	  -  SU -  -  -   S-  U -  n U S-  n X S-	  -  S-  S-  $ )	Niy+mr      r      =      g      A )tstates    r   rand_mulberry32.<locals>.rand,   sr    #z17mA&*4aLR!V,-;q@
N2g*,<<r   )returnfloatr   )seedr    r   s     @r   _mulberry32r%   (   s    :E
= Kr   c           	     r   [         R                  " U[         R                  S9n[        U5       H  nSnUS:X  a  U " 5       nUS:X  a  M  U " 5       n[	        [         R
                  " S[         R                  " U5      -  5      [         R                  " S[         R                  -  U-  5      -  5      X#'   M     U$ )zDBox-Muller on top of a uniform PRNG (matches player's `_gaussRand`).dtype        g       g       @)	npemptyfloat32ranger#   sqrtlogcospi)rngnoutiu1u2s         r   _gauss_rand_streamr8   ;   s    
((1BJJ
'C1XCiB CiUrwwtbffRj01BFF3;;K4LLM  Jr   c                &   [        U R                  [        R                  5      [	        U5      SS9n[	        [        R
                  " [        R                  " X"-  5      5      S-   5      nUS:  a  X#-  nUR                  [        R                  5      $ )zPlayer's `_gaussianSmooth(input, sigma, true)`: Gaussian filter with
edge-clamp boundary + per-stream RMS normalization to unit RMS.nearestsigmamodegư>r   )r   astyper*   r,   r#   r.   mean)xr<   r4   rmss       r   _smooth_rms_normalizerB   G   sj     AHHRZZ0e!*,C
	*+d2
3C
Qwi::bjj!!r   c                    [        U5      n[        US-  5      [        US-  5      [        US-  5      [        US-  5      S.n0 nUR                  5        H  u  pg[        Xp5      n[	        X5      XV'   M     U$ )zFour bilateral-correlated noise streams seeded from scenario_id.
Each stream is unit-RMS after Gaussian smoothing.
Keys: 'down', 'innerUp', 'outerUp', 'eyeSquint'.i  i  i  i  )downinnerUpouterUp	eyeSquint)r   r%   itemsr8   rB   )	Tscenario_idr<   r$   rngsstreamskr2   raws	            r   generate_noise_streamsrO   R   s{    
 [!D / / / /	D G**, (*36
  Nr   c           	     R   [        S[        X-  5      5      n[        R                  " U[        R                  S9nSnSn[        U 5      n[        U5       H  n	X-  n
[        X-   U5      nX:  a=  X
U n[        [        R                  " [        R                  " X-  5      5      S-   5      nOSnS[        [        R                  " U5      5      -  nXg-   U-
  U-  n[        [        R                  " USS5      5      XY'   M     [        US	S
S9R                  [        R                  5      $ )u   Per-frame soft silence indicator (0=speech, 1=silent).

Mirrors player's `_computeSilenceGate`: RMS per hop converted to dB,
soft sigmoid around -45 dB ± 12 dB, then σ=6 Gaussian smooth.
r   r'   g     Fg      (@g&.>g      4@r)         ?g      @r:   r;   )maxintr*   zerosr,   lenr-   minr#   r.   r?   log10clipr   r>   )wavsrrI   fpshoprN   
SILENCE_DB
SOFT_RANGEr3   r5   absegrA   dbgs                   r   silence_gate_from_wavrd   e   s     aRX
C
((1BJJ
'CJJCA1XGO5(C	 23d:;CCE"((3-(($r)Z7rwwq#s+,  S)<CCBJJOOr   c           	        US::  d  US::  a  U $ U R                   S   nUR                   S   U:w  af  UR                   S   U:  aN  XQR                   S   -
  n[        R                  " U[        R                  " XaS   [        R                  S9/5      nOUSU n[        XRUS9nU R                  [        R                  SS9nX1-  n	XS	   -  R                  [        R                  5      n
XS
   -  R                  [        R                  5      nXS   -  R                  [        R                  5      nX7S   -  R                  [        R                  5      n[         H  nUSS2U4   U
-   USS2U4'   M     USS2[        4   U-   USS2[        4'   [         H  nUSS2U4   U-   USS2U4'   M     [         H  nUSS2U4   U-   USS2U4'   M     [        R                  " USS5      R                  [        R                  5      $ )zBake tremor into `target` (T, 52) in-place-equivalent (returns new array).

Brow channels: jitter scaled by amp * silence_gate * filtered_noise.
EyeSquint channels: always-on, amp * filtered_noise (no gating).
Clamped to [0, 1].
r)   r   r'   N)r<   T)copyrD   rE   rF   rG   rQ   )shaper*   concatenatefullr,   rO   r>   BROW_DOWN_L_RBROW_INNER_UPBROW_OUTER_UP_L_REYE_SQUINT_L_RrX   )targetsilence_gaterJ   ampr<   rI   padrL   r4   r   j_downj_innerj_outerj_eyechs                  r   apply_tremorrx      s    czUc\QA!!a 1$((++C>>bggc+;2::N+ L (+L$Q5AG
--

-
.CA6?"**2::6F9%%--bjj9G9%%--bjj9G;''//

;EBZ&(ArE
 = 01G;C=BZ')ArE
  BZ%'ArE
 773S!((44r   )r   strr"   rS   )r$   rS   )r3   rS   r"   
np.ndarray)r@   rz   r<   r#   r"   rz   )      ?)rI   rS   rJ   ry   r<   r#   r"   dict)   )
rY   rz   rZ   rS   rI   rS   r[   rS   r"   rz   )gy&1?r{   )ro   rz   rp   rz   rJ   ry   rq   r#   r<   r#   r"   rz   )__doc__
__future__r   typingr   r   r   numpyr*   scipy.ndimager   rk   rl   rm   rn   r   r%   r8   rB   rO   rd   rx   r   r   r   <module>r      s    # ( (  +  &	" +."'26( &(P"P,6P< $ #	'5)'5!'5 '5 	'5 )3	'5r   