
    (HJj)             !       	   U d Z ddlZddlZddlZddlZddlZddl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mZmZmZ ddlmZ ddlmZmZmZmZ ddlmZ ddlmZ ddlZddl Z ddl!Z!ddl"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z)m*Z*m+Z+ dd	l,m-Z- dd
l.m/Z/m0Z0m1Z1 ddl2m3Z3 ddl4m5Z5 ddl6m7Z8 ddl6m9Z: ddl;m<Z<m=Z=m>Z>m?Z?m@Z@mAZA ddlBmCZCmDZDmEZEmFZFmGZG ddlHmIZImJZJ ddlKmLZL dedefdZM G d d      ZN e#       ZOde#deeP   fdZQ ej                  d      ZSeSr-eSj                  d      D  cg c]  } | j                          c} ndgZV eQeOeV       ede#fd       ZWeWeOj                  _Y         G d d e5      ZZ G d! d"e5      Z[ G d# d$e5      Z\ eN       Z] ej                         Z_da`eeD   ead%<   e G d& d'             Zbe G d( d)             Zce G d* d+             Zdd,ePfd-Zed,ePddfd.Zfd/d0hZg G d1 d2eC      Zh G d3 d4      Zi G d5 d6eC      Zj G d7 d8eC      ZkdeDfd9Zld:eEdeGfd;Zmd:eEd<e'fd=Znd:eEfd>ZoeOj                  d?      d@        ZqeOj                  dA      dB        ZreOj                  dA      d,ePfdC       ZteOj                  dA      d,ePfdD       ZveOj                  dE      dFeZd<e'fdG       ZweOj                  dH      ddIeeP   fdJ       ZxeOj                  dK       e$dL       e%dL       e%d       e%dM       e%dN       e%dO       e%dP       e%dM       e%d       e%dQ       e%d       e%dR       e%dM       e%d      fd<e'dSe)dIePdTeeP   dUeydVezdWe{dXezdYeydZeeP   d[ezd\eeP   d]ePd/eyd0eeP   fd^       Z|eOj                  d_       e$dL       e%d`       e%da       e%db       e%dc      fdSe)dIePddePdeePdfezf
dg       Z}	 ddhe*diej                  djezdTeeP   dkeydleyfdmZeOj                  dn      dhe*fdo       ZdpZdeez   fdqZdre{dseez   fdtZduej                  dvezdwezdej                  fdxZdyeeP   deeP   fdzZd{ZePead|<   i ZeePef   ead}<   dePfd~ZdePfdZeOj                  d      dhe*fd       Z G d d      Zd Zedk(  r e        yyc c} w )a  Main module for MLX Audio API server.

This module provides a FastAPI-based server for hosting MLX Audio models,
including Text-to-Speech (TTS), Speech-to-Text (STT), and Speech-to-Speech (S2S) models.
It offers an OpenAI-compatible API for Audio completions and model management.
    N)asynccontextmanager)asdict	dataclassis_dataclass)Path)AnyDictListOptional)unquote)	FastAPIFileFormHTTPExceptionRequestResponse
UploadFile	WebSocketWebSocketDisconnect)CORSMiddleware)JSONResponsePlainTextResponseStreamingResponse)RepositoryNotFoundError)	BaseModel)read)write)VAD_SAMPLE_RATEServerVadConfigStreamingVadTurnDetectionErrorTurnEventKindparse_turn_detection)BaseModelExecutionAdapterInferenceBrokerInferenceHandleInferenceRequestInferenceResultChunk)TTSBatchItemTTSBatchOptions)
load_modelobjreturnc                 R   t        |       rt        | t              st        |       } t        | t              r-| j                         D ci c]  \  }}|t        |       c}}S t        | t              r| D cg c]  }t        |       c}S t        | t              r.t        j                  |       ryt        j                  |       ry| S t        | t        j                        r7t        j                  |       ryt        j                  |       ryt        |       S | S c c}}w c c}w )zPRecursively sanitize NaN, Infinity, and -Infinity values for JSON serialization.N)r   
isinstancetyper   dictitemssanitize_for_jsonlistfloatnpisnanisinffloating)r,   kvitems       Z/Users/ahmed/devFolder/claude-voice/.venv/lib/python3.12/site-packages/mlx_audio/server.pyr3   r3   B   s     CC!6Sk#t47IIK@KDAq$Q''K@@	C	478CD!$'C88	C	88C=XXc]
	C	%88C=XXc]Sz
! A8s   
D8D$c                   4    e Zd Zd ZdefdZdedefdZd Zy)ModelProviderc                 D    i | _         t        j                         | _        y N)modelsasyncioLocklockselfs    r=   __init__zModelProvider.__init__]   s    13LLN	    
model_namec                 l    || j                   vrt        |      | j                   |<   | j                   |   S rA   )rB   r+   rG   rJ   s     r=   r+   zModelProvider.load_modela   s0    T[[(&0&<DKK
#{{:&&rI   r-   c                    K   | j                   4 d {    || j                  v r| j                  |= 	 d d d       d {    y	 d d d       d {    y7 C7 7 	# 1 d {  7  sw Y   y xY ww)NTF)rE   rB   rL   s     r=   remove_modelzModelProvider.remove_modelg   sN     999T[[(KK
+ 99 	 9999999s`   A2AA2AA2A A2AA2AA2A2A2A/#A&$A/+A2c                    K   | j                   4 d {    t        | j                  j                               cd d d       d {    S 7 87 # 1 d {  7  sw Y   y xY wwrA   )rE   r4   rB   keysrF   s    r=   get_available_modelsz"ModelProvider.get_available_modelsn   s7     999((*+ 999999sC   A%AA%#AA%AA%A%A"AA"A%N)	__name__
__module____qualname__rH   strr+   boolrN   rQ    rI   r=   r?   r?   \   s+    #'S 'S T ,rI   r?   appallowed_originsc                     | j                   D cg c]  }|j                  t        us| c}| _         | j                  t        |ddgdg       yc c}w )z5(Re)configure CORS middleware with the given origins.T*)allow_originsallow_credentialsallow_methodsallow_headersN)user_middlewareclsr   add_middleware)rX   rY   ms      r=   
setup_corsrd   w   s_     &&&a!%%~*E&C %ee  	s
   AAMLX_AUDIO_ALLOWED_ORIGINS,r[   c                   K   ~ 	 d  t         t         j                          d a y y # t         t         j                          d a w w xY wwrA   )INFERENCE_BROKERstop_and_join)rX   s    r=   app_lifespanrj      sN     $ '**,# ('**,# (s   A
( A
AA
c                   ^   e Zd ZU eed<   eed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dZ	edz  ed	<   dZ
edz  ed
<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dZedz  ed<   dZeed<   dZeed<   dZeed<   dZeed<   y)SpeechRequestmodelinputNinstructvoice      ?speedmalegenderpitcha	lang_code	ref_audioref_textgffffff?temperaturegffffff?top_p(   top_krepetition_penaltymp3response_formatFstreamg       @streaming_intervali  
max_tokensverbose)rR   rS   rT   rU   __annotations__ro   rp   rr   r5   rt   ru   rw   rx   ry   rz   r{   r}   intr~   r   r   rV   r   r   r   rW   rI   r=   rl   rl      s    JJHcDjE3:E54<FC$JE54<IsTz IsTz HcDj #K#E54<E3:'**"'OS4Z'FD ##JGTrI   rl   c                       e Zd ZU eed<   dZedz  ed<   dZeed<   dZe	ed<   dZ
eed	<   d
Ze	ed<   dZeed<   dZedz  ed<   dZe	ed<   dZedz  ed<   dZeed<   dZee   ed<   y)TranscriptionRequestrm   NlanguageFr      r         >@chunk_duration   frame_thresholdr   context   prefill_step_sizetextword_timestampstimestamp_granularities)rR   rS   rT   rU   r   r   r   rV   r   r   r   r5   r   r   r   r   r   r   r   r   rW   rI   r=   r   r      s    JHcDjGTJ NE OSFDGS4Z!s!D#*!OT!-1Xc]1rI   r   c                   ,    e Zd ZU eed<   eed<   eed<   y)SeparationResponsetargetresidualsample_rateN)rR   rS   rT   rU   r   r   rW   rI   r=   r   r      s    KMrI   r   rh   c                       e Zd ZU eed<   y)SpeechTaskPayloadrequestN)rR   rS   rT   rl   r   rW   rI   r=   r   r      s    rI   r   c                   J    e Zd ZU eed<   eed<   ej                  ed<   eed<   y)TranscriptionTaskPayloadr   filenameaudior   N)	rR   rS   rT   r   r   rU   r6   ndarrayr   rW   rI   r=   r   r      s    !!M::rI   r   c                   ^    e Zd ZU eed<   ej                  ed<   eed<   eed<   eed<   eed<   y)SeparationTaskPayloadrJ   r   r   descriptionmethodstepsN)rR   rS   rT   rU   r   r6   r   r   rW   rI   r=   r   r      s'    O::KJrI   r   rJ   c                 ,    t         j                  |       S rA   model_providerr+   rJ   s    r=   _load_model_for_inferencer      s    $$Z00rI   c                    K   	 t        j                  t        |        d{    y7 # t        $ r  t        $ r}t        dd| d      |d}~wt
        $ r}t        dd| d|       |d}~ww xY ww)	a  Load ``model_name`` synchronously and translate failures into HTTPException.

    Routes that return a ``StreamingResponse`` commit the HTTP status + headers
    before the body generator runs, so any failure inside the inference worker
    surfaces as 200 OK with an empty body. Pre-flighting the load here lets the
    framework's exception handlers turn the failure into a real HTTP error
    response. Warm models are a no-op (``ModelProvider.load_model`` is cached).
    N  zModel not found: z. is not a known HuggingFace repo or local pathstatus_codedetaili  zFailed to load model z: )rC   	to_threadr   r   r   	Exception)rJ   excs     r=   _preflight_model_loadr      s      9:FFF " &zn4bc
 	  *:.3%@
 	s<   A5) ') A5) A2AA2A--A22A5r   r   c                       e Zd ZdeddfdZy)STTExecutionAdapterr   r-   Nc           
         |j                   }t        j                  j                  |j                  xs d      \  }}|xs d}dt        j
                          d|j                   | }t        ||j                  |j                         	 t        |j                        }|j                  j                  dhd      }t        j                  |j                         }	|j#                         D 
ci c]  \  }
}|
|	j$                  v s|
t&        v r|
|  }}
} |j                   |fi |}t)        |d      rt)        |d	      rd}|D ]  }|j*                  j-                         r nt/        |t0              r||z  }||d
}n>|j2                  t5        |dd       t5        |dd       t5        |dd       t5        |dd       d}|j7                  t9        j:                  t=        |            dz           nJ|j*                  j-                         s0|j7                  t9        j:                  t=        |            dz          t        j                  j?                  |      rt        j@                  |       	 |jC                          y c c}}
w # t        j                  j?                  |      rt        j@                  |       w w xY w)N .mp3z/tmp/_rm   Texcludeexclude_none__iter____next__)r   accumulated
start_timeend_timeis_finalr   )r   startendr   r   
)"payloadospathsplitextr   time
request_idaudio_writer   r   r   rJ   r   
model_dumpinspect	signaturegenerater2   
parameters_STT_EXTRA_KWARGShasattrcancel_eventis_setr/   rU   r   getattr	emit_datajsondumpsr3   existsremove	emit_done)rG   r   r   r   extsuffixtmp_path	stt_model
gen_kwargsr   keyvalueresultaccumulated_textchunk
chunk_datas                   r=   
run_serialzSTTExecutionAdapter.run_serial  sg   ,3OO!!'"2"2"8b93499;-q););(<VHEHgmmW-@-@A%	$1'2D2DEI 33 	 4 J  )))*<*<=I #-"2"2"4"4JC)...#9J2J U
"4   (Y''?J?Fvz*wvz/J#% #E++224!%-(E1($)+;&
 %*JJ%,UL$%G#*5*d#C(/z4(H(/z4(H&
 %%djj1B:1N&ORV&VW# $$ ))002!!$**->v-F"G$"NOww~~h'		(#C< ww~~h'		(# (s    A%J 0#J D'J  J 7J=)rR   rS   rT   r'   r   rW   rI   r=   r   r     s    ."2 .t .rI   r   c                   ^    e Zd Z	 	 ddZedefd       ZdeddfdZddZ	de
ddfd	Zdd
Zy)_TTSAdapterContinuousSessionc                Z    || _         || _        i | _        t               | _        d| _        y )Nr   )adaptermodel_session	_requestsset_emitted_audio_next_sequence_id)rG   r   r   s      r=   rH   z%_TTSAdapterContinuousSession.__init__B  s,     *68(+!"rI   r-   c                 L    | j                   j                  xr | j                   S rA   )r   idler   rF   s    r=   r   z!_TTSAdapterContinuousSession.idleN  s     !!&&=t~~+==rI   r   Nc                    |j                   j                         r|j                          y |j                  }|j                  }| j
                  }| xj
                  dz  c_        || j                  |<   | j                  j                  t        ||j                  |j                  |j                  |j                  |j                  |j                  |j                   |j"                  	      g       y )N   )	sequence_idr   rp   ro   rr   rt   ru   rx   ry   )r   r   r   r   r   r   r   r   addr)   rn   rp   ro   rr   rt   ru   rx   ry   )rG   r   r   speech_requestr   s        r=   submitz#_TTSAdapterContinuousSession.submitR  s    &&(%,__ ,,!#&-{# +'--(..+44(..)00(..,66+44
	
rI   c                    | j                          | j                  j                         }|D ]V  }| j                  j	                  |j
                        }|,|j                  w|j                  |j                         |j                          | j                  j                  |j
                  d        | j                  j                  |j
                         |j                  |j                  dkD  r|j                  j                  }|j                   w|j                  t#        d             |j                          | j                  j                  |j
                  d        | j                  j                  |j
                         c| j$                  j'                  |||j                  |j                          | j                  j)                  |j
                         |j*                  s|j
                  | j                  vr|j                  t-        dd             |j                          | j                  j                  |j
                  d        | j                  j                  |j
                         Y y )Nr   z2TTS batch event included audio without sample_rate  No audio generatedr   )_cancel_disconnected_requestsr   stepr   getr   error
emit_errorr   popr   discardr   samplesr   r   r   
ValueErrorr   _emit_audior   doner   )rG   eventseventr   r   s        r=   r   z!_TTSAdapterContinuousSession.stepl  s   **,##((*Enn(():):;G{{&""5;;/!!#""5#4#4d;##++E,=,=>{{&5==1+<070G0G$$,&&"#WX %%'NN&&u'8'8$?''//0A0AB(("KK%%	 ##''(9(9:zz$$D,?,??&&%#>RS !!#""5#4#4d;##++E,=,=>K rI   r  c                     t        | j                  j                               D ]#  }|j                  |       |j	                          % | j                  j                          | j                  j                          y rA   )r4   r   valuesr  r   clearr   )rG   r  r   s      r=   failz!_TTSAdapterContinuousSession.fail  s\    DNN1134Gu% 5 	!!#rI   c                 T   t        | j                  j                               D ]  \  }}|j                  j	                         s!| j
                  j                  |       |j                          | j                  j                  |d        | j                  j                  |        y rA   )r4   r   r2   r   r   r   cancelr   r  r   r  )rG   r   r   s      r=   r   z:_TTSAdapterContinuousSession._cancel_disconnected_requests  s    $()=)=)?$@ K''..0%%k2NN{D1''4 %ArI   )r   TTSExecutionAdapterr-   N)rR   rS   rT   rH   propertyrV   r   r'   r   r   BaseExceptionr  r   rW   rI   r=   r   r   A  s^    
# '
# >d > >
. 
4 
4(?T$- $D $5rI   r   c                      e Zd ZdZd ZdefdZdedefdZdede	e
ef   fdZde
fd	Zded
edefdZdedefdZdedefdZdedefdZdedefdZdedefdZdedefdZdefdZdedededdfdZdeddfdZdee   ddfdZy)r  _mlx_audio_loaded_modelc                     t        j                  dd      }	 t        dt        |            | _        y # t
        $ r
 d| _        Y y w xY w)NMLX_AUDIO_TTS_MAX_BATCH_SIZE8r      )r   getenvmaxr   max_batch_sizer  )rG   raw_max_batch_sizes     r=   rH   zTTSExecutionAdapter.__init__  sE    YY'EsK	$"%a-?)@"AD 	$"#D	$s   3 AAr   c                     t        || j                  d       }|,t        |j                        }t	        || j                  |       |S rA   )r   _REQUEST_MODEL_ATTRr   rJ   setattrrG   r   rm   s      r=   _get_model_for_requestz*TTSExecutionAdapter._get_model_for_request  sB    !9!94@=-g.@.@AEGT55u=rI   r-   c                     |j                   j                  }t        |j                  |j                  |j
                  |j                  |j                  |j                  |j                  |j                  | j                  |j                  
      S )N)
rz   r{   r}   r~   r   rw   r   r   r  r   )r   r   r*   rz   r{   r}   r~   r   rw   r   r   r  r   rG   r   r   s      r=   _build_batch_optionsz(TTSExecutionAdapter._build_batch_options  sx    (/(?(?&22 && &&-@@%00$..!((-@@.."**
 	
rI   c                 V    |j                   j                  }|j                  dhd      S )Nrm   Tr   )r   r   r   r%  s      r=   _request_kwargsz#TTSExecutionAdapter._request_kwargs  s)    (/(?(?(('(NNrI   namec                     t        ||d       }t        |      sy t        |      j                  dk(  r|t	        |      vry |S )Nzunittest.mock)r   callabler0   rS   vars)rG   rm   r)  r   s       r=   _get_callable_model_attrz,TTSExecutionAdapter._get_callable_model_attr  sA    tT* ;!!_4T%[9PrI   
continuousc          	      \   |j                   j                  }|rdnd}| j                  ||      }t        |      r t	         |di | j                  |            S |j                  ry|j                  s|j                  ry|j                  dvry|j                  dvry|j                  dvryy)Nsupports_tts_continuous_batchsupports_tts_batchFNrs   Nrq   TrW   )r   r   r-  r+  rV   r(  r   rx   ry   rt   rr   ru   )rG   rm   r   r.  r   	hook_namehooks          r=   _model_supports_requestz+TTSExecutionAdapter._model_supports_request  s     )0(?(?/9+?S 	 ,,UI>D>=t33G<=>>  ##~'>'>  6{2{2rI   c                    | j                  |d      }|y| j                  ||d      sy|j                  j                  }|j                  ryt        j                  |      }|j                  }d|vry|j                  rd|vry|j                  rd|vry|j                  dvrd|vry|j                  d	vrd
|vry|j                  d	vrd|vry|j                  rd|vry|j                  rd|vryy)Nbatch_generateFr.  textsvoices	instructsr2  gendersr3  speedspitches
ref_audios	ref_textsT)r-  r6  r   r   r   r   r   r   rp   ro   rt   rr   ru   rx   ry   )rG   rm   r   r8  r   r   paramss          r=   _can_call_batch_generatez,TTSExecutionAdapter._can_call_batch_generate  s   66u>NO!++E7u+M(/(?(?  %%n5	%%& HF$:""{&'@  69F;R{2xv7M{2y7N##F(B""{&'@rI   c                 Z    | j                  |d      }|d uxr | j                  ||d      S )Ncreate_tts_batch_sessionTr9  )r-  r6  )rG   rm   r   create_sessions       r=   _can_call_continuous_sessionz0TTSExecutionAdapter._can_call_continuous_session  sF    66&
 T) 
d.J.J7t /K /
 	
rI   c                 H    | j                  |      }| j                  ||      S rA   )r#  rC  r"  s      r=   supports_batchz"TTSExecutionAdapter.supports_batch  s%    ++G4,,UG<<rI   c                 H    | j                  |      }| j                  ||      S rA   )r#  rG  r"  s      r=   supports_continuous_batchz-TTSExecutionAdapter.supports_continuous_batch  s%    ++G400@@rI   c                 Z   |j                   j                  }d|j                  |j                  |j                  |j
                  |j                  |j                  |j                  |j                  |j                  |j                  r|j                  |j                  fS d |j                  fS )Ntts)r   r   r   rw   rz   r{   r}   r~   r   rx   ry   r   r   r%  s      r=   	batch_keyzTTSExecutionAdapter.batch_key!  s    (/(?(?!!$$&&    --%%$$##1?1F1FN--""
 	
 MQ""
 	
rI   c                 $    | j                  |      S rA   )rN  )rG   r   s     r=   continuous_batch_keyz(TTSExecutionAdapter.continuous_batch_key2  s    ~~g&&rI   c                     | j                  |      }| j                  |d      }|t        d       || j                  |            }t	        | |      S )NrE  z/Model does not provide create_tts_batch_session)r   r   )r#  r-  NotImplementedErrorr&  r   )rG   r   rm   rF  r   s        r=   create_continuous_batch_sessionz3TTSExecutionAdapter.create_continuous_batch_session5  sd    ++G466&
 !%&WXX&t'@'@'IJ+'
 	
rI   r   r   Nc                     t        j                         }t        ||||j                         |j	                  |j                                y )Nformat)ioBytesIOr   r   r   getvalue)rG   r   r   r   r   buffers         r=   r  zTTSExecutionAdapter._emit_audioC  s@     !11		
 	&//+,rI   c                 D   |j                   }|j                  }| j                  |      }|j                  }|rwt	        |t
              rgt        j                  j                  |      st        dd|       ddl
m} t        |d      xr |j                  dk(  } |||j                  |      }g }d }	i d	|j                  d
|j                   d|j"                  d|j$                  d|j&                  d|j(                  d|d|j*                  d|j,                  d|j.                  d|j0                  d|j2                  d|j4                  d|j6                  d|j8                  d|j:                  }
 |j<                  |j>                  fi |
D ]  }|j@                  jC                         r&tE        jF                          |jI                           y |j4                  r)| jK                  |||jL                  |j                         x|jO                  |jL                         |	|j                  }	 |j4                  r|jI                          y |st        dd      tQ        jR                  |      }| jK                  ||||	       |jI                          y )Nr    Reference audio file not found: r   r   )
load_audio
model_typespark)r   volume_normalizerp   rr   rt   ru   ro   rw   rx   ry   rz   r{   r}   r~   r   r   r   r   r   )*r   r   r#  rx   r/   rU   r   r   r   r   mlx_audio.tts.generater]  r   r^  r   rp   rr   rt   ru   ro   rw   ry   rz   r{   r}   r~   r   r   r   r   r   rn   r   r   mxclear_cacher   r  r   appendr6   concatenate)rG   r   r   r   rm   rx   r]  	normalizeaudio_chunksr   generate_kwargsr   concatenated_audios                r=   r   zTTSExecutionAdapter.run_serialS  s   %,__ ++G4",,	Is377>>),# #=i[I 
 :|4T9I9IW9TI"!--!*I 
^))
^))
 n++
 ^))	

 //
 11
 
 //
 >55
 ^))
 ^))
 !."C"C
 n++
 !."C"C
 .33
  ~--!
& %enn^%9%9M_MF##**, !!#$$  "LL&&	 ##FLL1&"("4"4K! N$   C8LMM^^L9.2DkRrI   requestsc                     t        |      dk(  r j                  |d          y  j                  |d         t         fd|D              s|D ]  } j                  |        y |d   j                  j
                  }|D cg c]"  }|j                  j
                  j                  $ }}|D cg c]"  }|j                  j
                  j                  $ }}|D cg c]"  }|j                  j
                  j                  $ }}|D cg c]"  }|j                  j
                  j                  $ }}|D cg c]"  }|j                  j
                  j                  $ }}|D cg c]"  }|j                  j
                  j                  $ }	}|D cg c]"  }|j                  j
                  j                  $ }
}|D cg c]"  }|j                  j
                  j                  $ }} j                  d      }||D ]  } j                  |        y t        j                   |      }i d|d|d|d|d	|d
|	d|
d|d|j"                  d|j$                  d|j&                  d|j(                  d|j*                  d|j,                  ddd|j.                  }|j1                         D ci c]  \  }}||j2                  v s|| }}}|D cg c]  }g  }}|D cg c]  }d  }} |di |D ]o  }|j4                  }|dk  s|t        |      k\  r#||   }|j6                  j9                         rC||   j;                  |j<                         |j>                  ||<   q tA        |      D ]  \  }}|j6                  j9                         r|jC                          1||   }||   }|r|-|jE                  tG        dd             |jC                          lt        |      dk(  r|d   ntI        jJ                  |      } jM                  ||j                  j
                  ||       |jC                           y c c}w c c}w c c}w c c}w c c}w c c}w c c}w c c}w c c}}w c c}w c c}w )Nr   r   c              3   B   K   | ]  }j                  |        y wrA   )rC  ).0r   rm   rG   s     r=   	<genexpr>z0TTSExecutionAdapter.run_batch.<locals>.<genexpr>  s"      
IQgD))%9s   r8  r:  r;  r<  r>  r=  r?  r@  rA  rz   rw   r   r}   r{   r~   r   Fr   r   r   r   rW   )'lenr   r#  allr   r   rn   rp   ro   rr   rt   ru   rx   ry   r-  r   r   rz   rw   r   r}   r{   r~   r   r2   r   sequence_idxr   r   rd  r   r   	enumerater   r  r   r6   re  r  )rG   rj  r   first_speech_requestr:  r;  r<  r>  r=  r?  r@  rA  r8  r   kwargsr   r   r   audio_chunks_by_sequencesample_ratesr   rq  chunksr   r   rm   s   `                        @r=   	run_batchzTTSExecutionAdapter.run_batch  s   x=AOOHQK(++HQK8 
IQ
 
 $( $.6qk.A.A.I.I>FGh7((..hG?GHxG'//))//xHEMNX'W__,,55X	N?GHxG'//))//xHAIJg7??**11J@HIW7??**00IGOPxGgoo--77x
PEMNX'W__,,55X	N66u>NO!#( $%%n5	
U
f
 
 f	

 w
 w
 *
 
 /;;
 -77
 .99
 )//
 )//
 !"6"I"I
 e
  +33!
& *0
)7:33)BVBV;VCJ 	 
 BJ4JAR 4J8@)A1$)A$.v.F!..La<3x=#@|,G##**,$\299&,,G)/););L& / &/x%8!L'##**,!!#-l;F&|4K[0""!c:NO !!#!$V!1F1Ir~~f7ME''	 + &9g HHNHJIPN6
 5K)AsH   'Q2'Q'Q'Q9'Q&'Q 'Q% 'Q*6Q/Q/	Q5(	Q:)rR   rS   rT   r   rH   r'   r#  r*   r&  r1   rU   r   r(  r-  rV   r6  rC  rG  rI  rK  rN  rP  rS  rl   r   r  r   r4   rx  rW   rI   r=   r  r    sd   3$.> 
,< 
 
O'7 ODcN O	C 	 "
  
67G D >
;K 
PT 
=&6 =4 =A1A Ad A
!1 
c 
"',< ' '
7G 
-!- &-
 - 
- F"2 Ft FPV $'7"8 V T V rI   r  c                   .    e Zd Zd ZdefdZdeddfdZy)SeparationExecutionAdapterc                 .    d | _         d | _        d | _        y rA   )_model_name_model
_processorrF   s    r=   rH   z#SeparationExecutionAdapter.__init__  s    '+rI   rJ   c                    | j                   |k(  r$| j                  | j                  | j                  fS ddlm}m} |j                  |      | _        |j                  |      | _        || _         | j                  | j                  fS )Nr   )SAMAudioSAMAudioProcessor)r|  r}  r~  mlx_audio.stsr  r  from_pretrained)rG   rJ   r  r  s       r=   _get_resourcesz)SeparationExecutionAdapter._get_resources  sn    z)dkk.E;;//=+;;JG..z:%{{DOO++rI   r   r-   Nc           	         |j                   }dt        j                          d|j                   d}t        ||j                  |j
                         	 | j                  |j                        \  }} ||j                  g|g      }d|j                  dz  z  }|j                  |d}|j                  |j                  |j                  |j                  |j                  |d      }	t!        j"                          t%        j&                  |	j(                  d	         }
t%        j&                  |	j*                  d	         }|j
                  }d
 }|j-                  t/         ||
|       |||      |             t0        j2                  j5                  |      rt1        j6                  |       	 |j9                          y # t0        j2                  j5                  |      rt1        j6                  |       w w xY w)Nz/tmp/separation_r   z.wav)descriptionsaudios   )r   	step_size2   )r  r  
anchor_idsanchor_alignmentode_optode_decode_chunk_sizer   c                     t        j                         }t        || |d       |j                  d       t	        j
                  |j                               j                  d      S )NwavrU  r   utf-8)rW  rX  r   seekbase64	b64encoder   decode)audio_arraysrrZ  s      r=   audio_to_base64z>SeparationExecutionAdapter.run_serial.<locals>.audio_to_base64#  sH    FKEBA''6==gFFrI   )r   r   r   )r   r   r   r   r   r   r  rJ   r   r   r   separate_longr  r  r  r  rb  rc  r6   arrayr   r   r   r   r   r   r   r   r   )rG   r   r   r   rm   	processorbatchr  r  r   target_audioresidual_audior   r  s                 r=   r   z%SeparationExecutionAdapter.run_serial  s   )0%diik]!G4F4F3GtLHgmmW-@-@A(	$#2273E3EFE9%112 zE
 W]]Q./I!(iHG((||"// ++!&!7!7&( ) F NN88FMM!$45LXXfooa&89N++KG "*<E,^[I + ww~~h'		(# ww~~h'		(# (s   D&G   7G7)rR   rS   rT   rH   rU   r  r'   r   rW   rI   r=   rz  rz    s'    
	, 	,/"2 /t /rI   rz  c                      t         Zt               } | j                  dt                      | j                  dt	                      | j                  dt                      | a t         S )NsttrM  
separation)rh   r%   register_adapterr   r  rz  )brokers    r=   get_inference_brokerr  7  sX     "':'<=':'<=.H.JK!rI   handlec                 p   K   t        j                  | j                  j                         d {   S 7 wrA   )rC   r   result_queuer   )r  s    r=   _next_inference_chunkr  B  s)     ""6#6#6#:#:;;;;s   -646r   c                  K   	 	 t        |        d {   }|j                  dk(  rnq|j                  dk(  r|j                  |j                   t	        j
                  d       d {    |j                          d {   r| j                          n| j                          y 7 7 A7 +# | j                          w xY ww)Nr  r  r   )r  kindr  r   rC   sleepis_disconnectedr  )r  r   r   s      r=   _stream_inference_resultsr  F  s     /77EzzV#zzW$kk!----""",,...  	 8 #. 	sQ   CB0 B*AB0 *B,+B0 B.B0 C*B0 ,B0 .B0 0CCc                    K   d }	 	 t        |        d {   }|j                  dk(  r|| j                          S |j                  dk(  r|j                  |j                  }\7 M# | j                          w xY ww)Nr  r  )r  r  r  r  r   )r  r   r   s      r=   _await_inference_resultr  W  so     F	/77EzzV#
 		 zzW$kk!]]F 7 	s*   A:A% A#A% A:)A% %A77A:/c                     K   ddiS w)NmessagezMWelcome to the MLX Audio API server! Visit https://localhost:3000 for the UI.rW   rW   rI   r=   rootr  e  s      	b s   z
/v1/modelsc            	         K   t         j                          d{   } g }| D ]3  }|j                  |dt        t	        j                               dd       5 d|dS 7 Cw)zG
    Get list of models - provided in OpenAI API compliant format.
    Nrm   system)idobjectcreatedowned_byr4   )r  data)r   rQ   rd  r   r   )rB   models_datarm   s      r=   list_modelsr  l  sd     
 "6688FK!tyy{+$		
  k22 9s   A AAA c                 F   K   t         j                  |        dd|  ddS w)z
    Add a new model to the API.

    Args:
        model_name (str): The name of the model to add.

    Returns:
        dict (dict): A dictionary containing the status of the operation.
    successzModel z added successfullystatusr  r   r   s    r=   	add_modelr    s+      j)fZL@S,TUUs   !c                    K   t        |       j                  d      } t        j                  |        d{   }|rt	        d      S t        dd|  d      7 #w)	a   
    Remove a model from the API.

    Args:
        model_name (str): The name of the model to remove.

    Returns:
        Response (str): A 204 No Content response if successful.

    Raises:
        HTTPException (str): If the model is not found.
    "N   )r   r   zModel 'z' not foundr   )r   stripr   rN   r   r   )rJ   removeds     r=   rN   rN     sY      $**3/J"//
;;GC((gj\4UVV	 <s   3AA$Az/v1/audio/speechr   c           	        K   | j                   r]t        | j                   t              rCt        j                  j                  | j                         st        dd| j                          t        | j                         d{    t               j                  d| j                  t        |       | j                  dhd	      | j                  
      }t        t        ||      d| j                    dd| j                    i      S 7 w)z>Generate speech audio following the OpenAI text-to-speech API.r   r\  r   NrM  )r   rm   Tr   endpoint_kindrJ   r   normalized_kwargsr   zaudio/zContent-Dispositionzattachment; filename=speech.)
media_typeheaders)rx   r/   rU   r   r   r   r   r   rm   r  r   r   r   r   r   r  r   )r   r   r  s      r=   
tts_speechr    s      Z(9(93?ww~~g//09':K:K9LM 
  
...!#**==!'2!,,gYT,R~~ + F !&'2G3345!%A'BYBYAZ#[
  /s   BDDBDz/v1/audio/voicesrm   c                 Z  K   | st        dd      	 ddlm}  || dg      }t        |      dz  }|j                         sd| g dS t        d |j                  d      D              }d| |D cg c]  }||d	 c}dS # t        $ r}d| g t	        |      d	cY d
}~S d
}~ww xY wc c}w w)au  List available voices for a TTS model.

    Resolves the model's HF snapshot directory and enumerates
    ``voices/*.safetensors`` files (the convention used by Kokoro and
    similar voice-pack-based TTS models). Returns an empty ``data`` list
    for models that don't ship per-voice packs so callers can fall back
    to whatever defaults make sense for that model.
    r   z!model query parameter is requiredr   r   )snapshot_downloadzvoices/*.safetensors)repo_idallow_patternsr4   )r  rm   r  r  Nr;  )r  rm   r  c              3   4   K   | ]  }|j                     y wrA   )stem)rm  ps     r=   rn  ztts_voices.<locals>.<genexpr>  s     E$DqAFF$Ds   z*.safetensors)r  r)  )	r   huggingface_hubr  r   rU   r   is_dirsortedglob)rm   r  snapshote
voices_dirr;  r;   s          r=   
tts_voicesr    s      4WXXO5$+A*B
 h(*J 5"==EJOOO$DEEF/56v!1%v6   O 5"s1vNNO 7s@   B+B  AB+/B&;B+ 	B#	BB#B+B##B+z/v1/audio/transcriptions.Fr   r   r   r   ndjsonfiler   r   r   r   r   r   r   r   r   r   c                 r  K   t        ||||||||	|
|||      }|j                          d{   }t        j                  |      }t	        |d      \  }}|j                          t        |j                         d{    t               j                  d|j                  t        ||j                  xs d||      |j                  dhd	
      |j                        }|dv rd}d}	 	 t        |       d{   }|j                  dk(  rn|j                  dk(  r|j                   |j"                  }t%        |t&              r|j)                  dd      }t+        |      j-                         D ]t  }|j/                         }|s	 t1        j2                  |      }t%        |t6              rd|v sd|v r|}Gt%        |t6              sXd|v s]||j9                  d      xs dz  }v 	 |j;                          |d|i}|dk(  r,t=        |j9                  d      xs dj/                               S |dk(  r.t?        d|j9                  d      xs dj/                         i      S t?        |      S tA        tC        ||       d      S 7 \7 7 # t4        $ r Y *w xY w# |j;                          w xY ww)a6  Transcribe audio using an STT model.

    The default ``response_format`` (``ndjson``) preserves mlx-audio's native
    ``application/x-ndjson`` streaming transport, where each line is a JSON
    object emitted by the underlying STT model (text deltas while streaming,
    or the full whisper segment payload for batch transcription).

    For OpenAI Audio API compatibility, ``response_format`` also accepts:

    * ``text`` -- ``text/plain`` body with the final transcript only.
    * ``json`` -- ``application/json`` body shaped ``{"text": "..."}``.
    * ``verbose_json`` -- ``application/json`` body with the full payload
      from the underlying model (``text``, ``segments``, ``language``,
      ...), passed through unchanged.

    See https://platform.openai.com/docs/api-reference/audio/createTranscription
    )rm   r   r   r   r   r   r   r   r   r   r   r   NF	always_2dr  z	audio.mp3)r   r   r   r   rm   Tr   r  )r   r   verbose_jsonr   r  r  r  replace)errorssegmentsr   r   r   zapplication/x-ndjson)r  )"r   r   rW  rX  
audio_readcloser   rm   r  r   r   r   r   r   r  r  r  r   r/   bytesr  rU   
splitlinesr  r   loadsr   r1   r   r  r   r   r   r  )r   r  rm   r   r   r   r   r   r   r   r   r   r   r   r   r   r  tmpr   r  r  fullr   r   linerawr,   s                              r=   stt_transcriptionsr    s    F #%'+' 7G D
**T
C3%0IE2IIK

...!#**==(]]1k	
 ",,gYT,R~~ + F ::#	3F;;::'::(++%}}dE*;;wy;ADt9//1C))+C !"jjo "#t,"c)Z3->"#C.6S=#swwv'<"< 2  * MMO<K(Df$$dhhv&6&<"%C%C%EFFf$$((6*:*@b)G)G)I JKKD!!!&'2) y 
 /( < % ! ! MMOs   +J7J	AJ7?J A,J7-J" <J=BJ" J +J" J" J" -BJ7J7J" 	JJ" JJ" "J44J7z/v1/audio/separationsz"mlx-community/sam-audio-large-fp16speechmidpoint   r   r   r   c                 2  K   | j                          d{   }t        j                  |      }t        |d      \  }}|j	                          t               j                  d|t        ||||||      |||d      }	t        |	       d{   S 7 |7 w)a  Separate audio using SAM Audio model.

    Args:
        file: Audio file to process
        model: SAM Audio model name (default: mlx-community/sam-audio-large-fp16)
        description: Text description of what to separate (e.g., "speech", "guitar", "drums")
        method: ODE solver method - "midpoint" or "euler" (default: midpoint)
        steps: Number of ODE steps - 2, 4, 8, 16, or 32 (default: 16)

    Returns:
        JSON with base64-encoded target and residual audio, plus sample rate
    NFr  r  )rJ   r   r   r   r   r   )r   r   r   )r  rJ   r   r  )	r   rW  rX  r  r  r  r   r   r  )
r  rm   r   r   r   r  r  r   r  r  s
             r=   audio_separationsr  U  s     ( D
**T
C3%0IE2IIK!#**"%#
 '
 + F" )000- , 1s"   BBA7BBBB	websocketr  r   
is_partial	streamingc           	        K   dt        j                  |j                        j                  v }|r|r|j                  t	        j
                  |      d|d      }d}	|}
|D ]^  }t        |t              r|nt        |dt        |            }|	|z  }	t        |dd      }|r|
|}
| j                  d	|d
       d{    ` | j                  d|	d|
|d       d{    ydt        j                          d}t        |||       	 |j                  ||d      }t        |d      r!|j                  rt        |j                        nd}| j                  |j                  |t        |d|      |d       d{    t         j"                  j%                  |      rt!        j&                  |       yy7 7 7 ># t         j"                  j%                  |      rt!        j&                  |       w w xY ww)a  Handle both streaming and non-streaming model inference over WebSocket.

    Streaming models (whose generate() accepts a ``stream`` parameter) receive
    the audio as an ``mx.array`` and yield token deltas sent as
    ``{"type": "delta", "delta": "..."}`` messages, followed by a
    ``{"type": "complete", ...}`` message.

    Non-streaming models fall back to temp-file + batch generate, sending the
    legacy ``{"text": ..., "is_partial": ...}`` format.
    r   TF)r   r   r   r   r   r   Ndelta)r0   r  complete)r0   r   r  r   r  z/tmp/realtime_r   )r   r   r  )r   r  r   r  )r   r   r   r   rb  r  r/   rU   r   	send_jsonr   r   r   r  r3   r   r   r   r   r   )r  r   r  r   r   r  r  supports_streamresult_iterr   detected_languager   r  
chunk_langr   r   r  s                    r=   _stream_transcriptionr     s    & '"3"3I4F4F"G"R"RRO9((HH[!$5 ) 
 $ E#E3/WUFCPUJ5W  5 K 
D9J/7$.!%%w&GHHH ! !!"# -(
 	
 	
 $DIIK=5Hk;7	$''8U'SF 6:.6?? "&//2 
 %%"KK ( '
H E",	   ww~~h'		(# (= I	
& ww~~h'		(# (sO   B8G3:F3; G3F5*G3A1F9 8F79F9 =7G35G37F9 97G00G3z!/v1/audio/transcriptions/realtimec                   K   | j                          d{    	 | j                          d{   }|j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      }t        d	| d
| d| d|        t        d       t        j                  |      }t        d       t        j                  d      }d}t        ||z  dz        }	t        d|	 d| d| d       g }
t        |dz        }t        |dz        }t        |dz        }d}d}t        j                         }d}d}| j                  ddd       d{    t        d       	 	 | j                          d{   }d|v rt        j                  |d   t        j                        }t!        |      |	z  }d}d}t#        |      D ]H  }||	z  }||	z   }||| }t!        |      |	k(  s!	 |j%                  |j'                         |      rd}|d z  }J t        j                         }|r|j-                  t        j.                        d"z  }|
j1                  |       |d z  }d}|}t!        |
      |d#z  z  t!        |      k  rKt        d$| d%| d&t!        |
       dt!        |
      |z  d'd(	       n|d z  }|d)z  dk(  rt        d*| d+       ||z
  }d} d}!t!        |
      dkD  r|s6t!        |
      |k\  r(|r&d} t        d,||z  d'd-t!        |
      |z  d'd.       nb||k\  r1t!        |
      |k\  r#d}!t        d/|d'd0t!        |
      |z  d'd.       n,t!        |
      |k\  rd}!t        d1t!        |
      |z  d'd.       | rEt!        |
      |k\  r7|}"t        j2                  |
d|"       }#d}	 t5        | ||#||d|2       d{    |!rt!        |
      dkD  rt!        |
      }"t        j2                  |
      }#	 t5        | ||#||d|2       d{    g }
d}t        d6|" d|"|z  d'd7       n3d9|v r/	 t=        j>                  |d9         }&|&j                  d:      d;k(  rn	 	 | jC                          d{    y7 j7 T7 ,7 
# t        $ r Y 1w xY w# t(        t*        f$ r }t        d!|        d}|d z  }Y d}~d}~ww xY w7 # t        $ rU}ddl}$t9        |      }%|$j;                          t        d3|%        | j                  |%d4d5       d{  7   Y d}~^d}~ww xY w7  # t        $ rU}ddl}$t9        |      }%|$j;                          t        d8|%        | j                  |%d4d5       d{  7   Y d}~)d}~ww xY w# t        $ r Y =w xY w# t@        $ r Y Kt        $ rB}	 | j                  t9        |      d4d5       d{  7   n# t        $ r Y nw xY wY d}~d}~ww xY w7 # t        $ r Y yw xY w# 	 | jC                          d{  7   w # t        $ r Y w w xY wxY ww)<z%Realtime transcription via WebSocket.Nrm   z-mlx-community/whisper-large-v3-turbo-asr-fp16r   r   i>  r  TzConfiguration received: model=z, language=z, sample_rate=z, streaming=zLoading STT model...zSTT model loaded successfully      i  zVAD initialized: frame_size=z
 samples (zms at zHz)g      ?g      ?g      @r   FreadyzReady to transcriber  r  dtyper   zVAD error (assuming speech):       @r  zSpeech detected (r  z frames): buffer z.2fzs)   zSilence detected: skipped z chunksz1Processing initial chunk for real-time feedback: zs, total buffer: szProcessing due to silence gap: zs silence, buffer: z#Processing due to max buffer size: )r  r  z$Error during initial transcription: r  )r  r  zProcessed final chunk: zs), buffer clearedzError during transcription: r   actionstop)"acceptreceive_jsonr   printr   r+   	webrtcvadVadr   r   r  receiver   r6   
frombufferint16ro  range	is_speechtobytesr  OSErrorastypefloat32extendr  r   	tracebackrU   	print_excr   r  r   r  )'r  configrJ   r   r   r  r   vadvad_frame_duration_msvad_frame_sizeaudio_buffermin_chunk_sizeinitial_chunk_sizemax_chunk_sizesilence_skip_countspeech_chunk_countlast_speech_timesilence_threshold_secondsinitial_chunk_processedr  audio_chunk_int16
num_frames
has_speechspeech_framesiframe_start	frame_endframer  current_timeaudio_chunk_floattime_since_last_speechshould_process_initialshould_process_finalprocess_sizer  r  	error_msgr  s'                                          r=   stt_realtime_transcriptionsr9    s     


` --//ZZD

 ::j$/jj6JJ{D1	,ZLH:^\g[hhtu~t  A	

 	$%"--j9	-. mm
 !#[+@@4GH*>*:*EZD[[abmannqr	

 [3./ #
 #
 99;$'!"'!!WAV"WXXX#$ ) 1 1 33 '!$&MM''2B"(($S!
 !!23~E
"
 ! z*A"#n"4K +n <I-k)DE 5z^3	/"}}U]]_kJ-1
 - 2 +,  $yy{(9(@(@(Lw(V% ''(9:&!+&)*&'3$<(K!O<sCT?UU/a
|K\]`am]n\ooyz}  K  {L  MX  {X  Y\  z]  ]_  ` '!+&)B.!3 :;M:NgVW *68H)H&).&',$|$q( 4-1CC&15.OPbcnPnorOs  tE  FI  JV  FW  Xc  Fc  dg  Eh  hi  j
 /2KK-?/3,=>TUX=YYlmpq}m~  @K  nK  LO  mP  PQ  R \*n</3,A#lBST_B_`cAddef
 *c,.?CU.U#5L"$((<+F"GK.2+3%%''$'+&/  ( (C,=,A#&|#4L"$((<"8K3%%''$',&/   (*27/5l^:l[fNfgjMkk}~ 7"::gfo6Dxx)V3 4A Z	//###A  0V 	Y 4 4 !+G4 / "$A!"EF)-J)Q.M/R % ($'F	!++- DYKPQ'11&/7C  "" % ($'F	!++- <YKHI'11&/7C    !    	%%A'&JKKK 		 $ 			//### 		s  X>R X>V% RD*V% RV% ,R ?R	 R A*V% 0'RFV% 1S SS 0V% <T4 T1T4 0V% 6,V "V% 'X :W>;X ?X>V% V% 	R 	RV% RV% S
*S?V% S

V% S 	T.AT)T T)#V% )T..V% 1T4 4	V=AVVVV% VV% 	V"V% !V""V% %	W;.X 1W;: W!WW! W6!	W-*W6,W--W60X 6W;;X >X 	X
X>XX>X;X,%X(&X,+X;,	X85X;7X88X;;X>i]  c                  h    t        j                  d      } | sy 	 t        |       S # t        $ r Y y w xY w)N)MLX_AUDIO_REALTIME_TRANSCRIPTION_DELAY_MS)r   r  r   r  )r  s    r=   _default_transcription_delay_msr<    s7    
))?
@C3x s   
% 	11rz   delay_msc                    d|i}|2t        j                  | j                        }d|j                  v r||d<    | j                  di |S )zsOpen a streaming session, forwarding ``transcription_delay_ms`` only to
    models that declare the parameter.
    rz   transcription_delay_msrW   )r   r   create_streaming_sessionr   )rm   rz   r=  rt  sigs        r=   _open_streaming_sessionrB    sW     ";/F > >?#s~~5/7F+,)5))3F33rI   pcm16	from_rateto_ratec                 (   | j                  t        j                        dz  }||k(  s|j                  dk(  r|S t	        t        |j                  |z  |z              }|dk  r$|d| j                  t        j                  d      S t        j                  dd|j                  dt        j                  	      }t        j                  dd|dt        j                  	      }t        j                  |||      j                  t        j                        S )
zLinear resample int16 PCM to ``to_rate``.

    Returns float32 samples in [-1, 1]. If the rates already match, this is
    a plain dtype cast.
    r  r   r   NF)copy        rq   )numendpointr  )	r  r6   r  sizer   roundlinspacefloat64interp)rC  rD  rE  r  n_outsrc_xdst_xs          r=   _resample_pcm16_to_raterS    s     ll2::&0GGw||q0gllW,y89:Ezv%%bjju%==KKSgllU"**UEKKSee2::NE99UE7+222::>>rI   requested_modelc                     | xs dj                         }|r|S t        j                  d      xs dj                         }|xs dS )uP  Return the requested model ID, or the server-configured default.

    The default is read from ``$MLX_AUDIO_REALTIME_MODEL`` (settable via the
    ``--realtime-model`` CLI flag). Returns ``None`` when neither the client
    nor the server supplies a model — the caller is expected to reject the
    connection with a clear error.
    r   MLX_AUDIO_REALTIME_MODELN)r  r   r  )rT  
normalized
configureds      r=   _resolve_realtime_model_namerY    sH     "'R..0J))67=2DDFJrI   zmlx-community/silero-vad_REALTIME_VAD_MODEL_DEFAULT_realtime_vad_modelsc                  `    t        j                  d      xs dj                         xs t        S )zReturn the VAD model used for server-side turn detection.

    Read at request time from ``$MLX_AUDIO_VAD_MODEL`` (settable via the
    ``--vad-model`` CLI flag), defaulting to Silero.
    MLX_AUDIO_VAD_MODELr   )r   r  r  rZ  rW   rI   r=   _resolve_vad_model_namer^    s*     			'(.Beg-,-rI   r)  c                 b    t         j                  |       }|ddlm}  ||       }|t         | <   |S )zLoad and cache a streaming VAD model for server-side turn detection.

    Kept behind a module-level indirection so the realtime endpoint can be
    exercised with a fake VAD model in tests without hitting Hugging Face.
    r   )load)r[  r   mlx_audio.vadr`  )r)  rm   load_vads      r=   _load_realtime_vad_modelrc    s5     !$$T*E}2%*T"LrI   z/v1/realtimec           
          !"#$%&'K    j                          d{    dt        fddt        f fd"dt        f"fd} j                  j	                  d      }t        |        ) |d	       d{     j                          d{    y	 t        j                         t        d      s- |d d       d{     j                          d{    yd%t               &t        %&      #g dt        d'd}dt        fd}d7dt        dt        f"#fd"fd!d8!"#%&f	d}dt!        j"                         j$                  dd  $dt        f $'fd} "d |       d       d{    	 	  j'                          d{   }	 t)        j*                  |      }	|	j	                  dd      }
|
dk(  r|	j	                  d      xs i }|j	                  d       xs i j	                  d!      xs i }|j	                  d"      xs i }|j	                  d      xs |j	                  d      }|j	                  d#      xs i }|j	                  d$      }t/        |t              r|d%kD  r|t        |      }|rp| k7  rk	 t        j                  |      t        d      s |d|d       d{    D| t0        4 d{    t        %&      #ddd      d{    g dd&|v r@	 t3        |j	                  d&            ''d}n 	 t7        t9                     }t;        |'      } "d( |       d       d{    n|
d)k(  r|	j	                  d d      }|s%|# |        "d*d+dd,dd-igd.d/       d{    t=        j>                  tA        jB                  |      t<        jD                  0      }tG        |#jH                        }t0        4 d{    #jK                  |       ddd      d{     d1       d{    |1tG        |tL              }t0        4 d{    |jO                  |      }ddd      d{    D ]  }|jP                  tR        jT                  u rD# |        "d*d+dd,dd-igd.d/       d{     "d2|jV                  d3       d{    c "d4|jV                  d5       d{     |        d{    |jY                           nK|
d6k(  rF# |        "d*d+dd,dd-igd.d/       d{     |        d{    ||jY                          7 O7 7 # t        $ r7} |d
|        d{  7    j                          d{  7   Y d}~yd}~ww xY w7 7 7 67 # t(        j,                  $ r  |d       d{  7   Y ]w xY w# t        $ r} |d
|        d{  7   Y d}~d}~ww xY w7 O7 ?7 $# 1 d{  7  sw Y   5xY w# t4        $ r%} |t        |             d{  7   Y d}~d}~ww xY w# t        $ r#}d'd} |d'|        d{  7   Y d}~d}~ww xY w7 =7 7 7 # 1 d{  7  sw Y   xY w7 7 j7 L# 1 d{  7  sw Y   ]xY w7 7 7 7 7 7 # tZ        $ r Y nTt        $ rI}d%dl.}|j_                          	  |t        |             d{  7   n# t        $ r Y nw xY wY d}~nd}~ww xY w	  j                          d{  7   n# t        $ r Y nw xY wta        jb                          y# 	  j                          d{  7   n# t        $ r Y nw xY wta        jb                          w xY ww)9uU  OpenAI Realtime API-compatible WebSocket endpoint.

    Works with any STT model whose ``load_model`` result exposes the
    streaming-session protocol. A streaming session must provide:
      - ``feed(samples: np.ndarray[float32])``
      - ``close()``
      - ``step(max_decode_tokens: int) -> list[str]``
      - ``done: bool``
      - ``input_sample_rate: int``  (the native rate expected by ``feed``)
    Models that support an adjustable transcription-delay / latency knob
    should accept ``transcription_delay_ms: Optional[int]`` on
    ``create_streaming_session``; models without the concept can ignore it.

    Client → server events follow OpenAI Realtime (subset):
      ``session.update``, ``input_audio_buffer.append``,
      ``input_audio_buffer.commit``.
    Server → client events: ``session.created`` / ``session.updated``,
    ``conversation.item.added``, ``input_audio_buffer.committed``,
    ``conversation.item.input_audio_transcription.delta`` /
    ``.completed``, and ``error``.

    Turn detection follows OpenAI's ``turn_detection`` on
    ``session.audio.input``. With ``{"type": "server_vad"}`` the server runs a
    streaming VAD (Silero by default, see ``--vad-model``), emits
    ``input_audio_buffer.speech_started`` / ``input_audio_buffer.speech_stopped``
    and auto-commits each turn — the client never sends
    ``input_audio_buffer.commit``. With ``null`` (the default) the client drives
    commits manually. ``semantic_vad`` is not implemented yet.

    Model is selected via ``?model=<id>`` or ``session.update.model``;
    defaults to ``$MLX_AUDIO_REALTIME_MODEL``.
    Nr-   c                  J    dt        j                         j                  d d  S )Nevent_r  uuiduuid4hexrW   rI   r=   _new_event_idz"realtime_ws.<locals>._new_event_id1  s#    

(("-.//rI   r   c                 Z   K   d        i| } j                  |        d {    y 7 w)Nevent_id)r  )r   rk  r  s    r=   
send_eventzrealtime_ws.<locals>.send_event4  s,     }:':!!'***s    +)+r  c                 <   K    dd| id       d {    y 7 w)Nr  r  )r0   r  rW   )r  rn  s    r=   
send_errorzrealtime_ws.<locals>.send_error8  s     'Y4HIJJJs   rm   zsno realtime model configured: pass ?model=<id>, set session.update.model, or start the server with --realtime-modelzload failed: r@  zmodel z does not support streamingrH  rz   r=  c                  J    dt        j                         j                  d d  S )Nitem_r  rg  rW   rI   r=   _new_item_idz!realtime_ws.<locals>._new_item_id[  s#    tzz|'',-..rI   r  max_decode_tokensc                   K   t         4 d{    j                  |       }ddd      d{    D ](  }j                  |        dd|d       d{    * j                  S 7 _7 ?# 1 d{  7  sw Y   OxY w7 +w)uX  Run one session.step, ship deltas. Returns session.done.

        MLX runs inline on the event-loop thread (not via a worker thread):
        MLX streams are thread-bound, so all realtime MLX work — the
        transcription step and the VAD — must share one thread, otherwise you
        hit "no Stream(gpu, N) in current thread".
        Nru  z1conversation.item.input_audio_transcription.deltar   )r0   item_idcontent_indexr  )REALTIME_INFERENCE_LOCKr   rd  r  )ru  deltasr  current_item_idfull_text_partsrn  sessions      r=   drain_deltasz!realtime_ws.<locals>.drain_deltas^  s      +*\\4E\FF +*E""5)O.%&"	    || +****sM   B	A.B	A2B	A0*B	BB	0B	2B8A;9B B	c                  ^   K   dj                        }  dd| d       d {    y 7 w)Nr   z5conversation.item.input_audio_transcription.completedr   )r0   rx  ry  
transcript)join)r   r|  r}  rn  s    r=   	send_donezrealtime_ws.<locals>.send_donet  s8     ww'O*!""	
 	
 	
s   "-+-c                    	K    d dd       d{    t         4 d{    j                          ddd      d{     d       d{   s	  d       d{   s         d{    t         4 d{    t              ddd      d{    g d y7 7 7 m# 1 d{  7  sw Y   }xY w7 s7 c7 U7 H7 ,# 1 d{  7  sw Y   <xY ww)zCommit the current turn: emit ``committed``, drain the remaining
        transcription, emit ``completed``, then reopen a fresh session.

        Shared by the manual ``input_audio_buffer.commit`` path and the
        server-VAD auto-commit path.
        zinput_audio_buffer.committedN)r0   rx  previous_item_idr  rw  rq  )rz  r  rB  )	r|  r  r}  rm   r  rn  r~  rz   r?  s	   r=   finalize_turnz"realtime_ws.<locals>.finalize_turn  s      6*$(
 	
 	
 +*MMO +*$r::: %r:::k**-;9OG +* #	
 +****::*****s   C&B,C&B.C&B2C&B0C&CC&%C	&C&+
C&5C6C&CC&CC&"C#
C&.C&0C&2C8B;9C C&	C&C&C&C&C#CC#C&sess_r  c            	      N    dddd ddirj                         nd didS )	Nzrealtime.sessiontranscriptionrn   z	audio/pcm)r0   raterm   )rV  r  turn_detection)r  r  r0   rm   r   )to_dict)client_input_raterJ   
session_idturn_configs   r=   _session_snapshotz&realtime_ws.<locals>._session_snapshot  sH    (#'2<MN&-z%:?Jk&9&9&;PT
 	
rI   zsession.created)r0   r~  zinvalid JSONr0   r   zsession.updater~  r   rn   r  rV  r  r   r  zvad load failed: zsession.updatedzinput_audio_buffer.appendzconversation.item.addedzrealtime.itemuserinput_audio)r  r  r0   rolecontent)r0   r<   r  rw  z!input_audio_buffer.speech_started)r0   audio_start_msrx  z!input_audio_buffer.speech_stopped)r0   audio_end_msrx  zinput_audio_buffer.commit)r  r  )2r  rU   r1   query_paramsr   rY  r  r   r+   r   r   r<  rB  _REALTIME_DEFAULT_CLIENT_RATEr   rV   rh  ri  rj  receive_textr   r  JSONDecodeErrorr/   rz  r#   r!   rc  r^  r    r6   r  r  	b64decoder  rS  input_sample_ratefeedr   processr  r"   SPEECH_STARTEDaudio_ms
reset_turnr   r  r  rb  rc  )(r  rp  rT  r  turn_detectorrt  r  r  r  msgmsg_typesession_payloadaudio_inputtranscription_cfgresolved_modelfmtrequested_ratetarget_model_name	vad_model	audio_b64rC  r  vad_samplesturn_events
turn_eventr  rk  r  r|  r  r}  rm   rJ   r  rn  r~  r  rz   r?  r  s(   `                         @@@@@@@@@@@@@@r=   realtime_wsr    s    D 


03 0+$ +K# K  ,,009O-o>JN
 	
 	
 oo))*5 5456*/JKLLLooK<>%;1GG "$O%)O5-1K,0M/# /c $  ,	
 6 ))#2./0J
t 
 
 /<M<OP
QQQp!..00Cjjo
 wwvr*H++"%'')"4":.227;ArFFwOUSU$/OOO$D$J!!0!4!4W!= "ARAVAVB "ooh/52!$nc2~7I(6%$@$P!$):j)H! . 9 9:K L #5*DE($%6$99TU   !!2J66"9!(3%;#  76 ')O&*O#{2!&:'OO,<=' #*(,%(@ 7 9)I )5Y(L .;L;NO   88GGGR0	  #*}/D&2nO$$=&5*9(1(.-3],C+D%	   f&6&6y&AR1,g.G.G 32LL) 32 #Q777 ,"90/#K  76 '4&;&;K&H  76 '2
%??m.J.JJ.62>.&00I2A6E4=4:9?8O7P1*	%&'" !" !" #-,O6@6I6I/>!"#   #-,O4>4G4G/>!"#   #0/11)446A '2D 88"*&2nO$$=&5*9(1(.-3],C+D%	   $o%% ,!,,.}  	
 	   =,---oo 	M| R 1 ''  000* % !(=)<=== !
  76666 . !(Q000 !  ) %*.K,0M",/@-D"EEE$	%  32222 8  76666!" 2 &   	SV$$$ 			//### 		
		//### 		
s  _U4A_ U7_U:_U= 4_W _'W(B(_W_[4 *W	+[4 0W C[4 #W7 8[4 X"[4 &X%'[4 *X+9[4 X([4 Y ,[4 2Y2 [4 $Z!%A[4 )Z$*A[4 Z'[4 Z-[4 (Z*)[4 9[:"[4 [[4  [2[4 =[	>A[4 ["[4 ![%"[4  [([4 [+<[4 [.[4 [1[4 4_7_:_=	V=V8VV8-V0.V83_8V==___	[4 W4*W-+W40[4 3W44[4 7	X XXX[4 X[4 %[4 ([4 +X>1X42X>9[4 	Y/
Y*Y!Y*$[4 *Y//[4 2	Z;ZZZ[4 Z[4 $[4 '[4 *[4 -[ 3Z64[ ;	[4 [4 	[4 [[[	[4 %[4 ([4 +[4 .[4 1[4 4	]=^ ?]]\70\31\76]7	] ]]]^ ]^ ]. ']*(]. -_.	]:7_9]::__^.'^*(^.-_.	^:7_9^::__c                   "    e Zd ZddZd ZddZy)MLXAudioStudioServerc                 x    || _         d | _        t        |      | _        | j                  j	                  d       y )NT)exist_ok)start_ui
ui_processr   log_dirmkdir)rG   r  r  s      r=   rH   zMLXAudioStudioServer.__init__a  s0     G}D)rI   c                 l   t        t              j                  dz  }	 t        j                  ddgt        |      t        j                  t        j                  d       	 t        | j                  d	z  d
      }t        j                  g dt        |      |t        j                        | _        t!        d| j                   d       y# t        $ r t        d      t        j                  $ r}t        d|      d}~ww xY w# t        $ r t        d      t        $ r}t        d|       d}~ww xY w)z%Start UI with logs redirected to fileuinpminstallT)cwdstdoutstderrcheckuJ   ✗ Error: 'npm' is not installed or not found in PATH. UI will not start.u!   ✗ Error running 'npm install':
Nui.logw)r  rundev)r  r  r  u   ✓ UI started (logs: z/ui.log)uN   ✗ Error: 'npm' is not installed or not found in PATH. UI server not started.u   ✗ Failed to start UI: )r   __file__parent
subprocessr  rU   DEVNULLPIPEFileNotFoundErrorr   CalledProcessErroropenr  PopenSTDOUTr  r  )rG   ui_pathr  ui_logs       r=   start_ui_backgroundz(MLXAudioStudioServer.start_ui_backgroundg  s$   x.''$.	ENN	"L!))!	<$,,137F(..%L!((	DO *4<<.AB! ! 	\  ,, 	E@!DD	E ! 	`   	<6qc:;;	<s1   AC
  A)D 
&D0C<<DD3 D..D3c                    | j                   rb| j                          t        j                  d       t	        j
                  d       t        d| d|        t        d       t        d       n|rt        d| d|        t        d       	 t        j                  d	|||d
d       | j                  r%| j                  j                          t        d       | j                  dz  }|j                         r|j                          t        d|        y y # | j                  r%| j                  j                          t        d       | j                  dz  }|j                         r|j                          t        d|        w w xY w)Nr  zhttp://localhost:3000u"   ✓ API server starting on http://:u0   ✓ Studio UI available at http://localhost:3000z"
Press Ctrl+C to stop both serversu'   ✓ Realtime server starting on http://uA   ✓ Standard endpoints remain mounted; prefer realtime endpoints.zmlx_audio.server:appr   rC   )hostportreloadworkersloopu   ✓ UI server stoppedr  u   ✓ UI logs deleted from )r  r  r   r  
webbrowserr  r  uvicornr  r  	terminater  r   unlink)rG   r  r  r  realtimeui_log_paths         r=   start_serverz!MLXAudioStudioServer.start_server  sW   ==$$&JJqMOO346tfAdVDEDE78;D64&IJUV	AKK& ))+-.,,1K!!#""$1+?@ $ ))+-.,,1K!!#""$1+?@ $s   D A1F
N)Flogs)	localhost@  FF)rR   rS   rT   rH   r  r  rW   rI   r=   r  r  `  s    *#<JArI   r  c                     t        j                  d      } | j                  dddgd       | j                  dt        d	d
       | j                  dt        dd       | j                  dt
        dd       | j                  ddd       | j                  dt        dd       | j                  dt        d d       | j                  dt        d d       | j                  dt        d d       | j                  ddd        | j                  d!t        d d"       | j                         }|j                  r|j                  t        j                  d#<   |j                  &t        |j                        t        j                  d$<   |j                  r|j                  t        j                  d%<   |j                  &t        |j                        t        j                  d&<   t        t        |j                         t!        |j"                  |j$                  '      }|j'                  |j(                  |j*                  |j,                  |j.                  (       y ))NzMLX Audio API server)r   z--allowed-origins+r[   z List of allowed origins for CORS)nargsdefaulthelpz--hostr  zHost to run the server on)r0   r  r  z--portr  zPort to run the server onz--reloadFz!Enable auto-reload of the server.z
--start-ui
store_truez,Start the Studio UI alongside the API server)r
  r  z	--log-dirr  zDirectory to save server logsz--realtime-modelzbDefault model for /v1/realtime when the client omits ?model=. Overrides $MLX_AUDIO_REALTIME_MODEL.z!--realtime-transcription-delay-msa  Transcription latency/quality knob for streaming STT models that expose a ``transcription_delay_ms`` parameter (e.g. voxtral_realtime). Lower values reduce latency at the cost of accuracy. When unset, each model uses its own default. Overrides $MLX_AUDIO_REALTIME_TRANSCRIPTION_DELAY_MS.z--vad-modelzStreaming VAD model used for server-side turn detection on /v1/realtime (server_vad). Overrides $MLX_AUDIO_VAD_MODEL (default: mlx-community/silero-vad).z
--realtimez(Start the server for /v1/realtime usage.z--tts-max-batch-sizezmMaximum compatible TTS speech requests per continuous batch session. Overrides $MLX_AUDIO_TTS_MAX_BATCH_SIZE.rV  r;  r]  r  )r  r  )r  r  r  r  )argparseArgumentParseradd_argumentrU   r   rV   
parse_argsrealtime_modelr   environrealtime_transcription_delay_msr  tts_max_batch_sizerd   rX   rY   r  r  r  r  r  r  r  r  )parserargsclients      r=   mainr    si   $$1GHF
/	   sK6Q   sD/J   0	   ;  
 ,	   3   +:   3  	 7  
 7   D151D1D

-.++7BE00C


>? ~~,0NN

()*589P9P5Q

12sD(()!4==$,,OF
YYYY{{	  rI   __main__rA   )T)__doc__r  rC   r  r   rW  r   r   r  r   rh  r  
contextlibr   dataclassesr   r   r   pathlibr   typingr   r	   r
   r   urllib.parser   mlx.corecorerb  numpyr6   r  r  fastapir   r   r   r   r   r   r   r   r   fastapi.middleware.corsr   fastapi.responsesr   r   r   huggingface_hub.errorsr   pydanticr   mlx_audio.audio_ior   r  r   r   mlx_audio.realtime_vadr   r   r    r!   r"   r#   mlx_audio.server_inferencer$   r%   r&   r'   r(   mlx_audio.tts.continuousr)   r*   mlx_audio.utilsr+   r3   r?   rX   rU   rd   r  allowed_origins_envsplitr  default_originsrj   routerlifespan_contextrl   r   r   r   rD   rz  rh   r   r   r   r   r   r   r   r   r   r  rz  r  r  r  r  r   r  r  postr  deleterN   r  r  rV   r   r5   r  r  r   r   r  r9  r  r<  rB  rS  rY  rZ  r[  r^  rc  r  r  r  rR   )origins   0r=   <module>r     s       	  	     * 7 7  , ,      
 
 
 3 P P :  1 3   C &3 3 4, ,. iG d3i *  bii ;<   #6";";C"@A"@V\\^"@A
  3   $G $ $ +

 I ,29 2  &',,. .2 (?+ 2         1# 1C D 2 '(AB /3 /dc5 c5LJ 3 J Z
@!: @Fo < <<P <O g "/    3 3$ ,V V V LW3 W W* 
m g  6 	HSM  B 
$% Cyc"4jK4j J8;!$Z!$Zt*> K-1$Zoo
o o sm	o
 o o o o o c]o o 3-o o o &c]o &od 
!"Cy:;H~z"b)1
)1)1 )1 	)1
 )1 #)1f C$C$ C$ 	C$
 smC$ C$ C$L 23d d 4dV !& # 	45 	4HSM 	4?::?"%?03?ZZ?&(3- HSM  $> S =') d38n )- -3  ~O O Od
IA IAXaH zF E< Bs   R