
    jl                        d Z ddlmZ ddlmZmZmZ ddlZddl	m
Z
 dZdZdZd	Zd'dZd(dZd)dZd*dZ	 d+d,dZ	 d-d.d!Z	 	 d/d0d&ZdS )1a%  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         )      )      sstrreturnintc                D    d}| D ]}|t          |          z  dz  dz  }|S )zFNV-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)r   hcs      O/dataset/kemix-engine/package/face/animasync-face-v3/scripts/compiler/tremor.py	_hash_strr      s8     	A 3 3#a&&jH$
2H    seedc                    | dz  dfd}|S )zADeterministic uniform-[0,1) PRNG matching player's `_mulberry32`.r   r   floatc                     dz   dz  } | | dz	  z  d| z  z  dz  } | | | dz	  z  d| z  z  z   dz  | z  } | dz  } | | dz	  z  dz  dz  S )	Niy+mr      r      =      g      A )tstates    r   randz_mulberry32.<locals>.rand,   sx    #z117mA&*4AaLR!V,-;q@
Na2g*,<<r   )r   r   r"   )r   r%   r$   s     @r   _mulberry32r&   (   s2    :E
= 
= 
= 
= 
= 
= Kr   n
np.ndarrayc           	     j   t          j        |t           j                  }t          |          D ]}d}|dk    r |             }|dk     |             }t	          t          j        dt          j        |          z            t          j        dt           j        z  |z            z            ||<   |S )zDBox-Muller on top of a uniform PRNG (matches player's `_gaussRand`).dtype        g       g       @)	npemptyfloat32ranger   sqrtlogcospi)rngr'   outiu1u2s         r   _gauss_rand_streamr:   ;   s    
(1BJ
'
'
'C1XX N NCiiB CiiSUUrwtbfRjj011BF3;;K4L4LLMMAJr   xsigmar   c                @   t          |                     t          j                  t	          |          d          }t	          t          j        t          j        ||z                      dz             }|dk    r||z  }|                    t          j                  S )zPlayer's `_gaussianSmooth(input, sigma, true)`: Gaussian filter with
    edge-clamp boundary + per-stream RMS normalization to unit RMS.nearestr<   modegư>r   )r   astyper-   r/   r   r1   mean)r;   r<   r6   rmss       r   _smooth_rms_normalizerD   G   s     AHHRZ00e!*, , ,C
c	**++d2
3
3C
QwwCi::bj!!!r         ?Tscenario_iddictc                0   t          |          }t          |dz            t          |dz            t          |dz            t          |dz            d}i }|                                D ](\  }}t          ||           }t	          ||          ||<   )|S )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&   itemsr:   rD   )	rF   rG   r<   r   rngsstreamskr5   raws	            r   generate_noise_streamsrS   R   s    
 [!!D // // // //	 D G**,, 7 73 a((*366

Nr      wavsrfpsc           	        t          dt          ||z                      }t          j        |t          j                  }d}d}t          |           }t          |          D ]}	|	|z  }
t          |
|z   |          }||
k    rD| |
|         }t          t          j	        t          j
        ||z                      dz             }nd}dt          t          j        |                    z  }||z   |z
  |z  }t          t          j        |dd                    ||	<   t          |d	d
                              t          j                  S )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?   )maxr   r-   zerosr/   lenr0   minr   r1   rB   log10clipr   rA   )rU   rV   rF   rW   hoprR   
SILENCE_DB
SOFT_RANGEr'   r7   absegrC   dbgs                   r   silence_gate_from_wavrh   e   s2    aR#X

C
(1BJ
'
'
'CJJCA1XX 
- 
-GCOOq55ac(Cc	 2 233d:;;CCCE"(3--(((*$r)Z7rwq#s++,,AS)<<<CCBJOOOr   y&1?targetsilence_gateampc                >   |dk    s|dk    r| S | j         d         }|j         d         |k    rg|j         d         |k     rL||j         d         z
  }t          j        |t          j        ||d         t          j                  g          }n
|d|         }t          |||          }|                     t          j        d          }||z  }	|	|d	         z                      t          j                  }
|	|d
         z                      t          j                  }|	|d         z                      t          j                  }||d         z                      t          j                  }t          D ]}|dd|f         |
z   |dd|f<   |ddt          f         |z   |ddt          f<   t          D ]}|dd|f         |z   |dd|f<   t          D ]}|dd|f         |z   |dd|f<   t          j        |dd                              t          j                  S )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)copyrJ   rK   rL   rM   rY   )shaper-   concatenatefullr/   rS   rA   BROW_DOWN_L_RBROW_INNER_UPBROW_OUTER_UP_L_REYE_SQUINT_L_Rr_   )rj   rk   rG   rl   r<   rF   padrP   r6   r   j_downj_innerj_outerj_eyechs                  r   apply_tremorr}      s5    czzUc\\QA!!!a 1$$l(++C>bgc<+;2:NNN+  LL (+L$Q5AAAG
--
-
.
.ClA76?"**2:66F79%%--bj99G79%%--bj99G7;''//
;;E ) )BZ&(AAArE

= 01G;C= * *BZ')AAArE

 ( (BZ%'AAArE

73S!!((444r   )r   r   r   r   )r   r   )r'   r   r   r(   )r;   r(   r<   r   r   r(   )rE   )rF   r   rG   r   r<   r   r   rH   )rT   )
rU   r(   rV   r   rF   r   rW   r   r   r(   )ri   rE   )rj   r(   rk   r(   rG   r   rl   r   r<   r   r   r(   )__doc__
__future__r   typingr   r   r   numpyr-   scipy.ndimager   rs   rt   ru   rv   r   r&   r:   rD   rS   rh   r}   r"   r   r   <module>r      s2    # " " " " " ( ( ( ( ( ( ( ( ( (     + + + + + +        &	 	 	 	" " " " +.    ( &(P P P P P< $ #	'5 '5 '5 '5 '5 '5 '5r   