
    Pj#                       U d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
ZdZ e	e      j                         j                  j                  Zedz  Zd dZdZ ej(                  d	d
j+                  e      z   dz   ej,                        Z ej(                  dej,                        ZdddddddddddddZdddddddddddddZd!dZd!dZd"dZefd#dZej>                  jA                  dd      Z! e"ej>                  jA                  dd            Z# G d d      Z$da%de&d<    ejN                         Z(d$dZ)y)%u  Pocket TTS wrapper for the gateway — the cloned Jarvis voice, served over WS.

Ports the load / synth / mood-clip logic from ``voice/tts_pocket.py`` (Kyutai
Pocket TTS, MIT — a 100M CALM model that runs ~7x realtime on two CPU cores,
~40ms to first audio, ~200MB RAM), plus the emotion-tag parsing from
``voice/tts_chatterbox.py``. Reproduced here so ``gateway/`` is a standalone
deployable with no dependency on the ``voice/`` package.

Key differences from the desktop module:
  * The model is LAZY-loaded on the first voice synth (``get_tts()``); a
    chat-only WS session never imports or loads it. ``pocket_tts`` / torch are
    NOT in the base ``requirements.txt`` — install ``requirements-voice.txt``
    on the voice-capable deployment (see README).
  * Voice refs are read from ``$VOICE_DIR`` — default: the repo's ``voices/``
    dir for local dev, a Railway volume in prod. Voiceprints NEVER ship in git.
  * Output is 16-bit PCM WAV bytes per sentence (``synth_wav``), ready to push
    straight down the socket after its ``sentence`` frame.

Env: VOICE_DIR (voice-ref directory), POCKET_VOICE (catalog fallback voice),
VOICE_REF (base clone wav basename, default jarvis_ref.wav),
POCKET_MAX_TOKENS (default 300 ≈ 24s cap).
    )annotationsN)Pathi]  voicesc                 b    t        t        j                  j                  d      xs t              S )N	VOICE_DIR)r   osenvironget_DEFAULT_VOICE_DIR     9/Users/ahmed/devFolder/Ultron/claude-voice/gateway/tts.py	voice_dirr   *   s     

{+A/ABBr   )calmflatwarmhappyexcitedurgentalarmedsad	sarcasticdryseriousneutralz^\s*\[(|z)\]\s*z^\s*\[[a-z][a-z ,'-]{0,24}\]\s*r   r   r   r   r   )r   r   r   r   r   r   r   r   r   r   r   r   c                   t         j                  | xs d      }|r@| |j                         d j                         |j	                  d      j                         fS t        j                  | xs d      }|r|j	                  d      j                         dd j                         j                         }|r|j                         d   nd}| |j                         d j                         |t        v r|fS dfS | dfS )zStrip a leading [emotion] tag; return (clean_text, tag_name_or_None).

    Unknown tone tags are stripped too (a TTS must never read one aloud); their
    last word is tried as a known tag ("[Mildly excited]" -> "excited"). N   r   )
_TAG_REmatchendlstripgrouplower_ANY_TAG_REstripsplit_TAG_TO_STEM)textminnerkeys       r   split_emotionr/   S   s    
 	djb!AAEEGH~$$&
(8(8(:::$*"%A
  "1R(..0668#(ekkmBbAEEGH~$$&|0CNNNN:r   c                V    t        |       \  }}||rt        j                  |      fS dfS )zz(clean_text, protocol_emotion) for the `sentence` frame. protocol_emotion
    is one of warm|dry|excited|sad|calm or None.N)r/   _TAG_TO_PROTOCOLr
   )r+   cleantags      r   emotion_labelr4   c   s3     t$JE3#'',>>>>r   c                P    t        |       \  }}|rt        j                  |      S dS )zHThe mood-clip stem to voice this (tagged) sentence with, or None (base).N)r/   r*   r
   )r+   _cleanr3   s      r   stem_forr7   j   s(    %KFC$'<C 1T1r   c                $   | t        |       dk(  rd}n\t        j                  t        j                  | t        j                        dd      }|dz  j                  d      j                         }t        j                         }t        j                  |d	      5 }|j                  d
       |j                  d       |j                  |       |j                  |       ddd       |j                         S # 1 sw Y   |j                         S xY w)zFEncode a float32 [-1, 1] mono array as a complete 16-bit PCM WAV blob.Nr   r   dtypeg      g      ?g    @z<i2wbr      )lennpclipasarrayfloat32astypetobytesioBytesIOwaveopensetnchannelssetsampwidthsetframeratewriteframesgetvalue)samplesratepcmclippedbufws         r   float32_to_wav_bytesrS   s   s    #g,!+''"**WBJJ?sK ((/779
**,C	3		q	q	t	c	 

 <<> 

 <<>s   AC77DPOCKET_VOICEgeorgePOCKET_MAX_TOKENS300c                  @    e Zd ZdZd	dZd
dZddZddZddZddZ	y)	PocketTTSu   Loads pocket-tts, clones the base + mood voices, synthesizes WAV bytes.

    Constructing this LOADS the model — only do so from ``get_tts()`` on the
    first voice session.
    c                   ddl m} |j                         | _        t	        j
                         | _        i | _        d| _        t               }t        j                  j                  dd      }t        j                  j                  |      r|nt        ||z        }| j!                  |      | _        | j%                  |       y )Nr   )TTSModelF	VOICE_REFzjarvis_ref.wav)
pocket_ttsr[   
load_model_model	threadingLock_lock_states_clonedr   r   r	   r
   pathisabsstr
_load_base
_base_name_preload_emotions)selfr[   vdirref_namebase_refs        r   __init__zPocketTTS.__init__   s    '))+^^%
*,{::>>+/?@!ww}}X68Cx<P//(3t$r   c                   t         j                  j                  |      r]	 | j                  j	                  |      | j
                  d<   d| _        t        dt         j                  j                  |              |S | j                  j	                  t              | j
                  d<   t        S # t        $ r,}t        dt        |      d d  dt         d       Y d }~bd }~ww xY w)Nr   Tz  [tts] voice cloned from z  [tts] clone failed (P   u   ) — catalog '')r   re   isfiler_   get_state_for_audio_promptrc   rd   printbasename	Exceptionrg   _CATALOG_FALLBACK)rk   refes      r   rh   zPocketTTS._load_base   s    77>>#0#';;#I#I##NR #22773C3CC3H2IJK
  ;;AAR    0.s1vcr{m <+,A/ 0 00s   AB/ /	C$8"CC$c           
     .   | j                   sy t        j                  d t        j	                         D              D ]N  }|d| dz  }|j                         s	 | j                  j                  t        |            | j                  |<   P | j                  D cg c]  }|s|	 }}|r(t        dd	j                  t        |             d       y y # t        $ r2}t        d|j                   dt        |      d d  d       Y d }~d }~ww xY wc c}w )
Nc              3  &   K   | ]	  }|s|  y w)Nr   ).0ss     r   	<genexpr>z.PocketTTS._preload_emotions.<locals>.<genexpr>   s     !H-Ba!-Bs   jarvis_z.wavz  [tts] mood clip z	 failed (<   )z  [tts] mood clones ready (z, )rd   dictfromkeysr*   valuesis_filer_   rt   rg   rc   rw   ru   namejoinsorted)rk   rl   stemre   rz   kmoodss          r   rj   zPocketTTS._preload_emotions   s    ||MM!H\-@-@-B!HHDGD6..D<<>O%)[[%K%KI&T" I !LL.LqAL./		&-0H/IKL   O*499+Ys1vcr{m1MNNO.s$   1CD#D	D(D

Dc                    t        |      }t        |      \  }}|| j                  j                  |xs d| j                  d         fS )Nr   )r7   r/   rc   r
   )rk   r+   r   r2   _tags        r   
_state_forzPocketTTS._state_for   sB    ~#D)tdll&&tzr4<<3CDDDr   c                   | j                  |      \  }}|j                         s%t        j                  dt        j                        S | j
                  5  | j                  j                  ||t              }ddd       j                         j                         j                         j                  t        j                        j                  d      S # 1 sw Y   axY w)z?(optionally [emotion]-tagged) text -> float32 mono PCM @ 24kHz.r   r9   )
max_tokensNr    )r   r(   r>   zerosrA   rb   r_   generate_audio_MAX_TOKENSdetachcpunumpyrB   reshape)rk   r+   r2   statewavs        r   synthzPocketTTS.synth   s    t,u{{}88ARZZ00ZZ++,,UE8C - EC  zz|!'')00<DDRHH Zs   #CC"c                @    t        | j                  |      t              S )zD(optionally [emotion]-tagged) text -> complete 16-bit PCM WAV bytes.)rS   r   SAMPLE_RATE)rk   r+   s     r   	synth_wavzPocketTTS.synth_wav   s    #DJJt$4kBBr   N)returnNone)ry   rg   r   rg   )rl   r   r   r   )r+   rg   )r+   rg   r   
np.ndarray)r+   rg   r   bytes)
__name__
__module____qualname____doc__ro   rh   rj   r   r   r   r   r   r   rY   rY      s)    
%!M E
ICr   rY   zPocketTTS | None	_INSTANCEc                     t         %t        5  t         
t               a ddd       t         S t         S # 1 sw Y   t         S xY w)u   Load-on-first-use accessor. Raises if pocket-tts isn't installed — the
    caller turns that into an {"type":"error"} frame; chat still works.N)r   
_INIT_LOCKrY   r   r   r   get_ttsr      s:      %K	  9  s	   2A )r   r   )r+   rg   r   ztuple[str, str | None])r+   rg   r   z
str | None)rM   r   rN   intr   r   )r   rY   )*r   
__future__r   rD   r   rer`   rF   pathlibr   r   r>   r   __file__resolveparent
_REPO_ROOTr   r   	_EMOTIONScompiler   
IGNORECASEr!   r'   r*   r1   r/   r4   r7   rS   r	   r
   rx   r   r   rY   r   __annotations__ra   r   r   r   r   r   <module>r      sn  . # 	 	 	     (^##%,,33
(* C	 "**Z#((9"55	A2==
Q bjj;R]]K
 FvVI)u FvVI)u  ?2 ;F & JJNN>8< "**..!4e<=BC BCL #	 "Y^^
r   