o
    jl                     @  s   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dZd/ddZd0ddZd1ddZ	d2d3d d!Z	"d4d5d&d'Z	(	d6d7d,d-ZdS )8a%  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                 C  s&   d}| D ]}|t |A d d@ }q|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c r   O/dataset/kemix-engine/package/face/animasync-face-v3/scripts/compiler/tremor.py	_hash_str   s   r   seedc                   s   | d@  d fdd}|S )zADeterministic uniform-[0,1) PRNG matching player's `_mulberry32`.r   r   floatc                    sd    d d@   } | | d? A d| B  d@ } | | | d? A d| B   d@ | A } | d@ } | | d? A d@ d S )	Niy+mr      r      =      g      Ar   )tstater   r   rand,   s    z_mulberry32.<locals>.randN)r   r   r   )r   r!   r   r   r   _mulberry32(   s   r"   n
np.ndarrayc                 C  sr   t j|t jd}t|D ]*}d}|dkr|  }|dks|  }tt dt | t dt j |  ||< q|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u2r   r   r   _gauss_rand_stream;   s   2r5   xsigmar   c                 C  sR   t | tjt|dd}ttt|| d }|dkr#|| }|tjS )zPlayer's `_gaussianSmooth(input, sigma, true)`: Gaussian filter with
    edge-clamp boundary + per-stream RMS normalization to unit RMS.nearestr7   modegư>r   )r   astyper(   r*   r   r,   mean)r6   r7   r1   rmsr   r   r   _smooth_rms_normalizeG   s   r>         ?Tscenario_iddictc           	      C  sh   t |}t|dA t|dA t|dA t|dA d}i }| D ]\}}t|| }t||||< q!|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"   itemsr5   r>   )	r@   rA   r7   r   rngsstreamskr0   rawr   r   r   generate_noise_streamsR   s   




rL      wavsrfpsc                 C  s   t dt|| }tj|tjd}d}d}t| }t|D ]D}	|	| }
t|
| |}||
krC| |
| }tt	t
|| d }nd}dtt| }|| | | }tt|dd||	< qt|d	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      @r8   r9   )maxr   r(   zerosr*   lenr+   minr   r,   r<   log10clipr   r;   )rN   rO   r@   rP   hoprK   
SILENCE_DB
SOFT_RANGEr#   r2   absegr=   dbgr   r   r   silence_gate_from_wave   s    r`   y&1?targetsilence_gateampc                 C  s  |dks|dkr
| S | j d }|j d |kr;|j d |k r5||j d  }t|tj||d tjdg}n|d| }t|||d}| jtjdd}|| }	|	|d	  tj}
|	|d
  tj}|	|d  tj}||d  tj}tD ]}|dd|f |
 |dd|f< qx|ddtf | |ddtf< t	D ]}|dd|f | |dd|f< qt
D ]}|dd|f | |dd|f< qt|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)r7   T)copyrC   rD   rE   rF   rQ   )shaper(   concatenatefullr*   rL   r;   BROW_DOWN_L_RBROW_INNER_UPBROW_OUTER_UP_L_REYE_SQUINT_L_RrW   )rb   rc   rA   rd   r7   r@   padrI   r1   r   j_downj_innerj_outerj_eyechr   r   r   apply_tremor   s2   
" ""rt   )r   r   r   r   )r   r   )r#   r   r   r$   )r6   r$   r7   r   r   r$   )r?   )r@   r   rA   r   r7   r   r   rB   )rM   )
rN   r$   rO   r   r@   r   rP   r   r   r$   )ra   r?   )rb   r$   rc   r$   rA   r   rd   r   r7   r   r   r$   )__doc__
__future__r   typingr   r   r   numpyr(   scipy.ndimager   rj   rk   rl   rm   r   r"   r5   r>   rL   r`   rt   r   r   r   r   <module>   s(    

	

