+
    TV-jþ  ã                  óN  a € 0 t $ R t^ RIHt ^ RIHt ^ RIHt ^ RIH	t	H
t
 ^ RItRtR]R&   R	tR]R
&   R],          ],          tR]R&    ! R R]4      t]! RR7       ! R R4      4       tR R lt ! R R]]4      t]! RR7       ! R R4      4       t ! R R4      t ! R R4      tR# )uÒ  Server-side turn detection for the OpenAI-compatible ``/v1/realtime`` endpoint.

The realtime WebSocket endpoint transcribes a continuous audio stream, but on
its own it has no notion of *when a speaker's turn ends* â€” the client has to
send ``input_audio_buffer.commit`` by hand. This module adds OpenAI-compatible
``turn_detection`` so the server itself decides turn boundaries: it emits
``input_audio_buffer.speech_started`` / ``input_audio_buffer.speech_stopped``
and auto-commits, exactly like OpenAI's ``server_vad`` mode.

Only ``server_vad`` is implemented. It runs a streaming VAD model (Silero by
default) frame by frame and applies the same ``threshold`` /
``prefix_padding_ms`` / ``silence_duration_ms`` logic the OpenAI Realtime API
exposes. The endpointing logic (:class:`TurnDetector`) is a pure state machine,
deliberately free of any model dependency so it can be unit-tested with
synthetic probabilities; :class:`StreamingVad` adds the model and the framing.
)Úannotations)Ú	dataclass)ÚEnum)ÚListÚOptionalNi€>  ÚintÚVAD_SAMPLE_RATEi   ÚVAD_FRAME_SIZEg     @�@ÚfloatÚVAD_FRAME_MSc                  ó   € ] tR t^!tRtRtR# )ÚTurnDetectionErrorzGRaised when a client requests an unsupported ``turn_detection`` config.© N)Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__Ú__static_attributes__r   ó    Úg/Volumes/fast/ai/experiments/ui-tars-smoke/.venv/lib/python3.14/site-packages/mlx_audio/realtime_vad.pyr   r   !   s   † ÝQr   r   T)Úfrozenc                  óR   € ] tR t^%t$ RtRtR]R&   RtR]R&   RtR]R	&   R
 R lt	Rt
R# )ÚServerVadConfigz@Resolved ``server_vad`` parameters, mirroring the OpenAI schema.g      à?r
   Ú	thresholdi,  r   Úprefix_padding_msiô  Úsilence_duration_msc               ó   € V ^8„  d   QhRR/# )é   ÚreturnÚdictr   )Úformats   "r   Ú__annotate__ÚServerVadConfig.__annotate__-   s   € ÷ 
ñ 
˜ñ 
r   c                	óR   € R RRV P                   RV P                  RV P                  /# )ÚtypeÚ
server_vadr   r   r   ©r   r   r   ©Úselfs   &r   Úto_dictÚServerVadConfig.to_dict-   s0   € à�LØ˜Ÿ™Ø ×!7Ñ!7Ø! 4×#;Ñ#;ð	
ð 	
r   r   N)r   r   r   r   r   r   Ú__annotations__r   r   r*   r   r   r   r   r   r   %   s/   ‡ áJà€IˆuÓØ Ð�sÓ Ø"Ð˜Ó"÷
ñ 
r   r   c               ó    € V ^8„  d   QhRRRR/# )r   Úturn_detectionzOptional[dict]r   zOptional[ServerVadConfig]r   )r!   s   "r   r"   r"   6   s   € ÷ Jñ J¨ð JÐ<Uñ Jr   c           
     óŠ  € V '       g   R# V P                  R4      pVR8X  d‚   \        4       p\        \        V P                  RVP                  4      4      \	        V P                  RVP
                  4      4      \	        V P                  RVP                  4      4      R7      # VR8X  d   \        R	4      h\        R
V: 24      h)zäMap an OpenAI ``turn_detection`` object onto a :class:`ServerVadConfig`.

Returns ``None`` for ``null`` (manual-commit mode). Raises
:class:`TurnDetectionError` for ``semantic_vad`` (not implemented yet) or an
unknown ``type``.
Nr%   r&   r   r   r   r'   Úsemantic_vadz@semantic_vad is not supported by this server yet; use server_vadzunknown turn_detection type: )Úgetr   r
   r   r   r   r   r   )r.   Útd_typeÚdefaultss   &  r   Úparse_turn_detectionr4   6   sÁ   € ÷ ÙØ+×/Ñ/°Ó7€GØ�,ÔÜ$3Ó$5ˆÜÜ˜N×.Ñ.¨{¸H×<NÑ<NÓOÓPÜ!Ø×"Ñ"Ð#6¸×8RÑ8RÓSóô !$Ø×"Ñ"Ð#8¸(×:VÑ:VÓWó!ô
ð 	
ð �.Ô Ü ØNó
ð 	
ô Ð<¸W¹KÐHÓ
IÐIr   c                  ó   € ] tR t^RtRtRtRtR# )ÚTurnEventKindÚspeech_startedÚspeech_stoppedr   N)r   r   r   r   ÚSPEECH_STARTEDÚSPEECH_STOPPEDr   r   r   r   r6   r6   R   s   † Ø%€NØ%„Nr   r6   c                  ó0   € ] tR t^Wt$ RtR]R&   R]R&   RtR# )Ú	TurnEventzHA detected turn boundary. ``audio_ms`` is the offset from session start.r6   Úkindr   Úaudio_msr   N)r   r   r   r   r   r,   r   r   r   r   r<   r<   W   s   ‡ áRà
ÓØ‡Mr   r<   c                  óT   € ] tR t^_tRtR R ltR R lt]R R l4       tR R	 lt	R
t
R# )ÚTurnDetectoru  Pure endpointing state machine over per-frame speech probabilities.

Feed it one VAD probability per frame via :meth:`push`. It emits a
``SPEECH_STARTED`` when the probability first crosses ``threshold`` and a
``SPEECH_STOPPED`` once ``silence_duration_ms`` of sub-threshold audio has
elapsed after speech. ``prefix_padding_ms`` only shifts the reported
``audio_start_ms`` earlier â€” it does not gate transcription.

The running clock is kept across turns so reported offsets stay monotonic
for the lifetime of the session.
c               ó   € V ^8„  d   QhRR/# ©r   Úconfigr   r   )r!   s   "r   r"   ÚTurnDetector.__annotate__l   s   € ÷ &ñ &˜ñ &r   c                	ó<   € Wn         R V n        RV n        R V n        R# )ç        FN©Ú_configÚ_elapsed_msÚ
_in_speechÚ_silence_ms)r)   rC   s   &&r   Ú__init__ÚTurnDetector.__init__l   s   € Ø(.ŒØ"%ˆÔØ %ˆŒØ"%ˆÖr   c               ó$   € V ^8„  d   QhRRRRRR/# )r   Úprobabilityr
   Úframe_msr   úList[TurnEvent]r   )r!   s   "r   r"   rD   r   s!   € ÷ ñ  ð °ð ¸?ñ r   c                	óÆ  € V ;P                   V,          un         . pWP                  P                  8¬  pV P                  '       gƒ   V'       dy   R V n        RV n        V P                   V,
          V P                  P
                  ,
          pVP                  \        \        P                  \        ^ \        V4      4      4      4       V# V'       d
   RV n        V# V ;P                  V,          un        V P                  V P                  P                  8¼  dK   RV n        RV n        VP                  \        \        P                  \        V P                   4      4      4       V# )TrF   F)rI   rH   r   rJ   rK   r   Úappendr<   r6   r9   Úmaxr   r   r:   )r)   rO   rP   ÚeventsÚ	is_speechÚstarts   &&&   r   ÚpushÚTurnDetector.pushr   s  € Ø×Ò˜HÕ$ÕØ"$ˆØ%¯©×)?Ñ)?Ñ?ˆ	Ø��ˆßØ"&�”Ø#&�Ô à×$Ñ$ xÕ/°$·,±,×2PÑ2PÕPð ð —‘Üœm×:Ñ:¼CÀÄ3ÀuÃ:Ó<NÓOôð ˆ÷ Ø#&�Ô ð ˆð × Ò  HÕ,Õ Ø×#Ñ# t§|¡|×'GÑ'GÔGØ&+�D”OØ'*�DÔ$Ø—M‘MÜ!¤-×">Ñ">ÄÀD×DTÑDTÓ@UÓVôð ˆr   c               ó   € V ^8„  d   QhRR/# ©r   r   Úboolr   )r!   s   "r   r"   rD   Ž   s   € ÷ ñ ˜4ñ r   c                	ó   € V P                   # ©N)rJ   r(   s   &r   Ú	in_speechÚTurnDetector.in_speech�   s   € à�‰Ðr   c               ó   € V ^8„  d   QhRR/# ©r   r   ÚNoner   )r!   s   "r   r"   rD   ‘   s   € ÷ ñ ˜Dñ r   c                ó"   € RV n         RV n        R# )z=Clear speech state after a turn is committed; keep the clock.FrF   N)rJ   rK   r(   s   &r   Ú
reset_turnÚTurnDetector.reset_turn‘   s   € àˆŒØˆÖr   rG   N)r   r   r   r   r   rL   rX   Úpropertyr_   re   r   r   r   r   r@   r@   _   s/   † ñ
õ&õð6 ôó ð÷ñ r   r@   c                  óT   € ] tR t^—tRtR R ltR R lt]R R l4       tR R	 lt	R
t
R# )ÚStreamingVadau  Drive a :class:`TurnDetector` from a streaming VAD model.

``vad_model`` must expose the Silero streaming protocol:
``initial_state(sample_rate=...)`` and
``feed(chunk, state, sample_rate=...) -> (probability, state)``, consuming
fixed :data:`VAD_FRAME_SIZE`-sample windows at :data:`VAD_SAMPLE_RATE`.
Audio that doesn't fill a whole frame is buffered until the next call.
c               ó   € V ^8„  d   QhRR/# rB   r   )r!   s   "r   r"   ÚStreamingVad.__annotate__¡   s   € ÷ Añ A¨/ñ Ar   c                	óÊ   € Wn         W n        VP                  \        R 7      V n        \        V4      V n        \        P                  ! ^ \        P                  R7      V n
        R# )©Úsample_rate)ÚdtypeN)Ú_vadrH   Úinitial_stater   Ú_stater@   Ú	_detectorÚnpÚzerosÚfloat32Ú_buffer)r)   Ú	vad_modelrC   s   &&&r   rL   ÚStreamingVad.__init__¡   sC   € ØŒ	Ø(.ŒØ×-Ñ-¼/Ð-ÓJˆŒÜ'3°FÓ';ˆŒÜ#%§8¢8¨A´R·Z±ZÔ#@ˆŽr   c               ó    € V ^8„  d   QhRRRR/# )r   Úsamplesz
np.ndarrayr   rQ   r   )r!   s   "r   r"   rk   ¨   s   € ÷ ñ ˜zð ¨oñ r   c                ó¶  € ^ RI Hp VP                  '       dE   \        P                  ! V P
                  VP                  \        P                  4      .4      V n        . pV P
                  P                  ^ ,          \        8¼  dÕ   V P
                  R\         pV P
                  \        R V n        V P                  P                  W@P                  \        R7      w  qPn        VP                  V4       \        \        P                   ! V4      P#                  R4      ^ ,          4      pVP%                  V P&                  P)                  V\*        4      4       Kú  V# )zŽFeed 16 kHz float32 ``samples``; return any turn events detected.

Runs MLX work, so call it from a worker thread rather than the event
loop.
Nrm   éÿÿÿÿ)Úmlx.coreÚcoreÚsizert   Úconcatenaterw   Úastyperv   Úshaper	   rp   Úfeedrr   r   Úevalr
   ÚarrayÚreshapeÚextendrs   rX   r   )r)   r{   ÚmxrU   ÚframerO   Úprobs   &&     r   ÚprocessÚStreamingVad.process¨   sì   € õ 	à�<�<ˆ<ÜŸ>š>¨4¯<©<¸¿¹ÌÏ
É
Ó9SÐ*TÓUˆDŒLØ"$ˆØ�l‰l× Ñ  Õ#¤~Ô5Ø $§¡¨_¬nÐ =ˆEØŸ<™<¬¨Ð8ˆDŒLØ'+§y¡y§~¡~Ø—{‘{´ð (6ó (Ñ$ˆKœð �G‰G�KÔ Ü¤§¢¨Ó 5× =Ñ =¸bÓ AÀ!Õ DÓEˆDØ�M‰M˜$Ÿ.™.×-Ñ-¨d´LÓAÖBØˆr   c               ó   € V ^8„  d   QhRR/# r[   r   )r!   s   "r   r"   rk   ¿   s   € ÷ (ñ (˜4ñ (r   c                	ó.   € V P                   P                  # r^   )rs   r_   r(   s   &r   r_   ÚStreamingVad.in_speech¾   s   € à�~‰~×'Ñ'Ð'r   c               ó   € V ^8„  d   QhRR/# rb   r   )r!   s   "r   r"   rk   Â   s   € ÷ $ñ $˜Dñ $r   c                	ó:   € V P                   P                  4        R # r^   )rs   re   r(   s   &r   re   ÚStreamingVad.reset_turnÂ   s   € Ø�‰×!Ñ!Ö#r   )rw   rH   rs   rr   rp   N)r   r   r   r   r   rL   rŒ   rg   r_   re   r   r   r   r   ri   ri   —   s0   † ñõAõð, ô(ó ð(÷$ñ $r   ri   )Ú__conditional_annotations__r   Ú
__future__r   Údataclassesr   Úenumr   Útypingr   r   Únumpyrt   r   r,   r	   r   Ú
ValueErrorr   r   r4   Ústrr6   r<   r@   ri   )r”   s   @r   Ú<module>rœ      s¹   øðôõ" #å !Ý ß !ã ð €�Ó Ø€�Ó Ø˜~Õ-°Õ?€ˆeÓ ?ôR˜ô Rñ �$Ô÷
ð 
ó ð
õ Jô8&�C˜ô &ñ
 �$Ô÷ð ó ð÷5ñ 5÷p,$ó ,$r   