+
    Sjr                    <   R t ^ RIHt ^ RIt^ RIt^ RIt^ RIt^ RIHt ^ RI	t
^ RIt ^ RIHt Rt]P$                  ! 4       t]P$                  ! 4       tRs]P,                  ! 4       t]P0                  P3                  RR4      t]P0                  P3                  R	R
4      tR R ltR R ltR R ltR R ltR R lt R R lt!R R lt"R0R R llt#Rt$Rt%]! ]&4      PO                  4       PP                  PP                  R,          R,          t)]! ]&4      PO                  4       PP                  PP                  R,          R,          t* ! R  R!4      t+R" R# lt,R$ R% lt-R1R& R' llt. ! R( R)]+4      t/ ! R* R+]+4      t0 ! R, R-]+4      t1 ! R. R/4      t2R#   ] d    Rt ELji ; i)2u;  Full-duplex audio: continuous 16 kHz mic capture + interruptible playback.

Mic runs in a callback thread and feeds an asyncio queue of 32 ms frames
(512 samples @ 16 kHz — the frame size Silero VAD expects).

Playback keeps its own OutputStream; stop() cuts audio within one block
(~10 ms) for instant barge-in.
)annotationsN)Path)keyboardTFMUTE_KEYz<ctrl>+<alt>+m	BREAK_KEYz<ctrl>+<alt>+.c                   V ^8  d   QhRR/#    returnNone )formats   "voice/audio_io.py__annotate__r   ,   s     E Ed E    c                 T    \         P                  4        \        R\         R24       R# )z7Request an immediate abort of the current turn/control.u&   
  BREAK — aborting current action ()N)_breaksetprintBREAK_HOTKEYr   r   r   signal_breakr   ,   s    
JJL	3L>
CDr   c                   V ^8  d   QhRR/# r	   r
   boolr   )r   s   "r   r   r   2   s       r   c                 f    \         P                  4       '       d   \         P                  4        R# R# )z@True (and clears) if a break was requested since the last check.TF)r   is_setclearr   r   r   break_requestedr   2   s    }}r   c                    V ^8  d   QhRRRR/# r	   mutedr   r
   r   r   )r   s   "r   r   r   :   s     = =t = =r   c                F   \         ;_uu_ 4        V '       d   \        P                  4        M\        P                  4        \        pRRR4       Xe    VP                  V 4       R# R#   + '       g   i     L*; i  \         d   p\        RT 24        Rp?R# Rp?ii ; i)zCSet the mute flag AND release / re-acquire the physical mic device.Nz#  [mute] mic device toggle failed: )
_mute_lock
_mic_mutedr   r   _active_mic_on_mute_change	Exceptionr   )r!   mes   &  r   _apply_muter*   :   su    	NN 
 	}	=e$  
  	=7s;<<	=s#   8A)A< )A9	<B BB c                    V ^8  d   QhRRRR/# r    r   )r   s   "r   r   r   I   s     E ET Ed Er   c                    \         P                  4       \        V 4      8X  d   R# \        V 4       \	        V '       d	   R4       R# R\
         R24       R# )uH   Public entry — hotkey, HUD mic button, or control file all route here.Nu?   
  MIC MUTED — device released (earbuds back to hi-fi output)u'   
  MIC UNMUTED — device re-acquired (r   )r$   r   r   r*   r   MUTE_HOTKEY)r!   s   &r   	set_mutedr.   I   sF    d5k)	 M E4[M
CEr   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   S   s     ' 'd 'r   c                 J    \        \        P                  4       '       * 4       R # N)r.   r$   r   r   r   r   _toggle_muter2   S   s    *##%%&r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   W   s      D r   c                    \         '       d^    \        P                  ! \        \        \
        \        /4      p RV n        V P                  4        \        R\         R\
         R24       M\        R4       R	 R
 lp\        P                  ! VRR7      pVP                  4        R#   \         d   p\        RT R24        Rp?LORp?ii ; i)a  Global hotkey (Ctrl+Alt+M) + terminal fallback ('m'+Enter).

The global hotkey needs macOS Input Monitoring/Accessibility for the
terminal app (System Settings -> Privacy & Security); if that's not
granted it silently receives nothing, so the stdin fallback matters.
Tz  mute hotkey: u    · break/abort: z; (global; needs Input Monitoring) or type m/stop+Enter herez  [mute] global hotkey failed (z); use m+EnterNz;  [mute] pynput missing; mute with m+Enter in this terminalc                   V ^8  d   QhRR/# r   r   )r   s   "r   r   (_start_mute_hotkey.<locals>.__annotate__k   s       r   c                     ^ RI p V P                   FG  pVP                  4       P                  4       pVR9   d   \	        4        K4  VR9   g   K=  \        4        KI  	  R# )    N)r(   muteunmute)stopbreakabortx)sysstdinstriplowerr2   r   )r?   linews      r   
stdin_loop&_start_mute_hotkey.<locals>.stdin_loopk   sC    IID

""$A++55 r   targetdaemon)
_PYNPUT_OK_kbGlobalHotKeysr-   r2   r   r   rI   startr   r'   	threadingThread)hkr)   rE   ts       r   _start_mute_hotkeyrR   W   s     z	G""K$0,$@ ABBIHHJOK=0A, PO O P
 	KL 	
48AGGI  	G3A3nEFF	Gs   AB% %C	0CC	c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   x   s      $ r   c                 *    \         P                  4       # r1   )r$   r   r   r   r   is_mutedrU   x   s    r   c                    V ^8  d   QhRRRR/# )r	   floorintr
   r   r   )r   s   "r   r   r   |   s       T r   c                H   ^ RI pVP                  R8w  d   R#  \        P                  ! . RORR^R7      p\	        VP
                  P                  4       4      pW08  d3   \        P                  ! RRRV  2.R^R7       \        R	V R
V  24       R# R#   \         d     R# i ; i)u   Apple's VoiceProcessingIO permanently drags the system input gain
down (documented behavior) — a day of sessions left it at 27/100 and
the assistant went deaf. Self-heal at startup.Ndarwin	osascript-eTcapture_outputtexttimeoutzset volume input volume r^   r`   z  input volume was u&   /100 (VPIO gain-drag) — restored to )r[   r\   z%input volume of (get volume settings))	r?   platform
subprocessrunrX   stdoutrA   r   r'   )rW   r?   outcurrents   &   r   restore_input_volumerh   |   s     
||xnnHdA
 cjj&&()?NNd&>ug$FG#Q 'y 1!!&) *   s   A5B B! B!i>  i   helperaecmicsysaudioc                  >    ] tR t^tRtR R ltR R ltR R ltRtR	# )
_FrameQueuez8Shared drop-oldest frame delivery into the asyncio loop.c                   V ^8  d   QhRR/# r	   loopzasyncio.AbstractEventLoopr   )r   s   "r   r   _FrameQueue.__annotate__   s     L L6 Lr   c                	J    Wn         \        P                  ! R R7      V n        R# )   )maxsizeN)_loopasyncioQueueframes)selfrp   s   &&r   __init___FrameQueue.__init__   s    
18s1Kr   c                    V ^8  d   QhRRRR/# r	   frame
np.ndarrayr
   r   r   )r   s   "r   r   rq      s     	 	 	 	r   c                	    V P                   P                  V4       R #   \        P                   dX     T P                   P	                  4        T P                   P                  T4        R #   \        P
                   d      R # i ; ii ; ir1   )rx   
put_nowaitrv   	QueueFull
get_nowait
QueueEmptyry   r~   s   &&r   _put_nowait_FrameQueue._put_nowait   sm    	KK""5)   	&&(&&u-%% 	s&    B5A--BBBBc                    V ^8  d   QhRRRR/# r}   r   )r   s   "r   r   rq      s      j T r   c                	x     V P                   P                  V P                  V4       R #   \         d     R # i ; ir1   )ru   call_soon_threadsafer   RuntimeErrorr   s   &&r   _deliver_FrameQueue._deliver   s3    	JJ++D,<,<eD 		s   &* 99)ru   rx   N)	__name__
__module____qualname____firstlineno____doc__rz   r   r   __static_attributes__r   r   r   rm   rm      s    BL	 r   rm   c                   V ^8  d   QhRR/# )r	   r
   
int | Noner   )r   s   "r   r   r      s     4 4J 4r   c                 z  a
  \         P                  ! 4       p \        P                  P                  RR4      P                  4       pT'       d    \        T4      #  \         P                  P                  ^ ,          pRpRRrv\        T 4       EF  w  r#TR,          ^ 8:  d   K  TR,          P                  4       o
Rp\        ;QJ d    T
3R lT 4       F  '       g   K   RM	  R	M! T
3R lT 4       4      '       d
   T^d,          pR
S
9   g   RS
9   d
   T^
,          pRS
9   d
   T^,          p \         P                  ! TR,          4      R,          P                  4       p	RT	9   d   T^,          pMRT	9   d
   T^,          pY$8X  d
   T^,          pY8  g   EK  Y(rvEK  	  T#   \         d     R# i ; i  \         dY    \        T 4       FE  w  r#TR,          ^ 8  g   K  TP                  4       TR,          P                  4       9   g   KA  Tu u # 	   ELi ; i  \         d    Rp ELi ; i  \         d     Li ; i)u  Choose the microphone.

MIC_DEVICE env wins (an index, or a name substring). Otherwise score all
input devices: real microphones beat virtual ones (Steam Streaming,
VB-Cable, Stereo Mix, Sound Mapper...), and WASAPI beats MME/DirectSound —
Windows wireless-headset mics through MME often deliver audio too quiet
for the VAD to ever trigger. Returns a device index, or None = default.
N
MIC_DEVICE max_input_channelsname        c              3  ,   <"   T F	  qS9   x  K  	  R # 5ir1   r   .0bns   & r   	<genexpr>%_pick_input_device.<locals>.<genexpr>        #s!Avs   TF
microphonemicheadsethostapiwasapiwdm-ks
steamvirtualcableloopbackvoicemeeterz
stereo mixzsound mapperzprimary soundwavezline ing    e)sdquery_devicesr'   osenvirongetrA   rX   
ValueError	enumeraterB   defaultdeviceanyquery_hostapis)devswantid
default_inbadbest_ibest_ssapir   s             @r   _pick_input_devicer      s   ! ::>>,+113D	t9ZZ&&q)
MC4F$!"a'fIOO3#s#333#s###HA1
GA>FA	##AiL1&9??AC3QSQ ?FA:F-  . MS    	!$*+a/ JJLAfIOO,==H (	  
,  		s`   F  
F2 !H ,AH,/H, F/.F/2&H'H	HHHH)(H),H:9H:c                   V ^8  d   QhRR/# )r	   r
   zlist[int | None]r   )r   s   "r   r   r      s     , ,+ ,r   c                   a
  \         P                  ! 4       p . pRp \         P                  P                  ^ ,          p\        T 4       EF  w  rETR,          ^ 8:  d   K  TR,          P                  4       o
Rp\        ;QJ d    T
3R lT 4       F  '       g   K   RM	  RM! T
3R lT 4       4      '       d
   T^d,          pRS
9   g   R	S
9   d
   T^
,          pR
S
9   d
   T^,          p \         P                  ! TR,          4      R,          P                  4       pRT9   d   T^,          pMRT9   d
   T^,          pYC8X  d
   T^,          pTR8  g   EK  TP                  Yd34       EK!  	  TP                  RR7       T UUu. uF  w  rTNK	  	  p	ppT	P                  R4       T	#   \         d    R.u # i ; i  \         d    Rp ELi ; i  \         d     Li ; iu uppi )a  All plausible mics, best first, ending with the system default (None).

A device can EXIST yet deliver pure zeros (a wireless dongle with the
headset off, a dead WASAPI endpoint...), so callers should PROBE each
candidate and take the first one that produces actual signal.
Nr   r   r   c              3  ,   <"   T F	  qS9   x  K  	  R # 5ir1   r   r   s   & r   r   $_input_candidates.<locals>.<genexpr>  r   r   TFr   r   r   r   r   r   )reverser   i)r   r   r'   r   r   r   rB   r   r   appendsort)r   scoredr   r   r   r   r   r   _srf   r   s             @r   _input_candidatesr      s   ! ')FMCZZ&&q)
 $!"a'fIOO3#s#333#s###HA1
GA>FA	##AiL1&9??AC3QSR ?FAs7MM1&!-  . KKK,23F52QFC3JJtJG  v  
&  		 4sA   F& !F: )AG,GG&F76F7:G
GGGc               $    V ^8  d   QhRRRRRR/# )r	   devr   secondsfloatr
   r   )r   s   "r   r   r     s!     	 	z 	E 	E 	r   c                    \         P                  ! \        V\        ,          4      \        ^RV R7      p\         P                  ! 4        \        \        P                  ! V4      P                  4       4      #   \         d    Ru # i ; i)u   Record briefly from `dev`; return the peak. A LIVE mic always shows
ambient noise > 0 — an exact 0.0 means the endpoint is dead/silent.float32)
sampleratechannelsdtyper         )
r   recrX   SAMPLE_RATEwaitr   npabsmaxr'   )r   r   r   s   && r   _probe_devicer     sa    ffS;./Ky>
	RVVC[__&'' s   A4A7 7BBc                     a  ] tR tRtRtRtRtRtR V 3R lltR R	 lt	R
 t
R R ltR R lt]R R l4       tR R ltRtV ;t# )	MicStreami%  aU  Raw mic capture (sounddevice) with device auto-pick + auto-gain.

Auto-gain: wireless/USB headset mics on Windows often capture speech at
~0.03 peak, far below what the VAD reads as voice. We track the rolling
peak and scale frames toward a working level before the VAD sees them.
MIC_GAIN env forces a fixed gain instead (e.g. MIC_GAIN=6).
g333333?g       @g~jth?c                   V ^8  d   QhRR/# ro   r   )r   s   "r   r   MicStream.__annotate__2  s      6 r   c                	@  < \         SV `  V4       R V n        R V n        \        P
                  P                  RR4      P                  4       P                  4       pV'       d   VR8w  d   \        V4      MR V n
        V P                  ;'       g    RV n        RV n        R # )NMIC_GAINr   auto      ?r   )superrz   _stream_devicer   r   r   rA   rB   r   _fixed_gain_gain
_roll_peak)ry   rp   g	__class__s   && r   rz   MicStream.__init__2  sv    .2#'JJNN:r*00288:'(Q&[58d%%,,
r   c                    V ^8  d   QhRRRR/# )r	   r~   r   r
   r   )r   s   "r   r   r   ;  s     ' '* ' 'r   c                	"   V P                   e)   \        P                  ! WP                   ,          RR4      # \        \        P                  ! V4      P                  4       4      p\        W P                  R,          4      V n        V P                  V P                  8  db   \        V P                  V P                  V P                  ,          4      pV ;P                  RW0P                  ,
          ,          ,          un        WP                  ,          p\        \        P                  ! V4      P                  4       4      pVR8  d3   V ;P                  RV,          ,          un        WP                  ,          p\        P                  ! VRR4      # )Nr   gX9v?g?g333333?r   )r   r   clipr   r   r   r   _FLOORmin	_MAX_GAIN_TARGETr   )ry   r~   peakrH   rf   outpeaks   &&    r   _agcMicStream._agc;  s    '775#3#33T3??RVVE]&&()dOOe$;<??T[[()GHFJJ#**!455Jjj  s)*T>JJ$.(J**$CwwsD#&&r   c                	    \         P                  4       '       d   R # V P                  V P                  VR,          4      P	                  \
        P                  4      4       R # )N):NNNr8   )r$   r   r   r   astyper   r   )ry   indata_frames_timestatuss   &&&&&r   	_callbackMicStream._callbackN  s<    diit-44RZZ@Ar   c                    V ^8  d   QhRRRR/# r    r   )r   s   "r   r   r   S  s     B BT Bd Br   c           	     	   V'       d=   V P                   R uq n         Ve$    VP                  4        VP                  4        R # R # V P                   f[    \        P
                  ! V P                  \        ^R\        V P                  R7      V n         V P                   P                  4        R # R #   \         d     R # i ; i  \         d   p\        RT 24        R p?R # R p?ii ; i)Nr   r   r   r   r   	blocksizecallbackz$  [mic] reopen after unmute failed: )r   r;   closer'   r   InputStreamr   r   FRAME_SAMPLESr  rM   r   )ry   r!   r   r)   s   &&  r   r&   MicStream._on_mute_changeS  s     "llDOA|}FFHaggi 
 \\!B!~~<<K!#}!^^ 
 ""$ " !   B<QC@AABs*    B/ AC /B>=B>C%C  C%c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r   h  s     DA DAt DAr   c           
     	   \         P                  P                  R R4      P                  4       pV'       d   VP	                  4       '       g   VP                  4       p \        \        P                  ! 4       4       UUu. uF4  w  r4VR,          ^ 8  g   K  W$R,          P                  4       9   g   K2  VNK6  	  ppp. pV F&  p\        V4      R8  g   K  VP                  V4       K(  	  We,          pV'       g   \        4       .pMV'       d   \        V4      .pM. p. p\        4        F  p\        V4      p	V P                  V4      p
V	R8  d'   \!        RV
 RV	R R24       VP                  V4        M8\!        RT
 R	V	R8X  d   R
MR 24       V	R8X  g   Ko  VP                  V4       K  	  VP#                  V4       V'       g   R.pRpV F  p \        P$                  ! V\&        ^R\(        V P*                  R7      V n        V P,                  P/                  4        Wpn        V sV P                  V4      V n        V P6                  '       d   RV P6                   2MRp\!        RV P4                   RV R24        R# 	  \9        RV R24      hu uppi   \         d    . p ELi ; i  \         d6   pTp\!        RT P                  T4       RT 24       RT n         Rp?EK  Rp?ii ; i)r   r   r   r   r   z  mic probe: u    — live (peak z.4fr   u    — silentunavailableNr   r	  zfixed xr   z  mic device: z (gain: z$; MIC_DEVICE / MIC_GAIN to override)z  mic open failed on z: zno usable microphone ()r   r   r   rA   isdigitrB   r   r   r   r'   r   r   r   rX   r   	_dev_namer   extendr  r   r  r  r   rM   r   r%   device_namer   r   )ry   forcedr   r   r   matches
candidatesr   r  r   r   last_errgainr)   s   &             r   rM   MicStream.starth  s   b1779&..** <<>D)223C3C3E)F :)F 459 #y'88 1)F :
 J %+%%c*  !J023
,/K=J
 J')F(*$S)~~c*#:M$/?SzKL%%c*dV5%)S[mDF G3;MM#& + f%"V
%)C$!~~*#+!^^  ""$""#'>>##6 8<8H8H8H'$"2"2!34# t'7'7&8 G; < =' 0 3H:Q?@@y:  j  $-dnnS.A-B"QCHI#$sI   !J ?JJ3J9J B*J)J J&%J&)K)4)K$$K)c                    V ^8  d   QhRRRR/# )r	   r   r   r
   strr   )r   s   "r   r   r     s      z c r   c                	     V e   T M \         P                  P                  ^ ,          p\         P                  ! V4      R,          #   \         d     R# i ; i)Nr   r   )r   r   r   r   r'   )r   idxs   & r   r  MicStream._dev_name  sI    	#bjj.?.?.BC##C(00 		s   AA AAc                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r     s        d  r   c                	    V P                   e>   V P                   P                  4        V P                   P                  4        R V n         R # R # r1   r   r;   r  ry   s   &r   r;   MicStream.stop  8    <<#LLLL DL $r   )r   r   r   r   r   r  )r   r   r   r   r   r   r   r   rz   r   r  r&   rM   staticmethodr  r;   r   __classcell__r   s   @r   r   r   %  sZ     GIF '&B
B*DAL     r   r   c                     a  ] tR tRtRtR V 3R llt]R R l4       tR R ltR	 R
 lt	R R lt
R R ltR R ltR R ltRtV ;t# )AECMicStreami  uS  Echo-cancelled mic via helper/aecmic (Apple VoiceProcessingIO).

Audio played by this Mac — including our own TTS — is subtracted
from the mic signal in hardware-adjacent DSP, so barge-in detection
never hears the assistant's own voice. Silence between utterances is
gated to literal zeros by Apple's noise suppression; that's normal.
c                   V ^8  d   QhRR/# ro   r   )r   s   "r   r   AECMicStream.__annotate__  s     > >6 >r   c                	  < \         SV `  V4       R V n        R V n        \        P
                  ! 4       V n        \        P
                  ! 4       V n        \        \        P                  P                  RR4      4      V n        R # )NAEC_GAINz1.0)r   rz   _proc_threadrN   Event	_stopping_waker   r   r   r   r   ry   rp   r   s   &&r   rz   AECMicStream.__init__  sW    .2
04"*__&
 2::>>*e<=
r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r/    s     $ $t $r   c                 	*    \         P                  4       # r1   )
AEC_HELPERis_filer   r   r   	availableAECMicStream.available  s    !!##r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r/    s      t r   c                	    V s \        P                  4       '       g   V P                  4        \        P
                  ! V P                  R R7      V n        V P                  P                  4        R# )TrG   N)	r%   r$   r   _spawnrN   rO   _readerr3  rM   r&  s   &r   rM   AECMicStream.start  sG      ""KKM ''t||DIr   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r/    s       r   c                   V P                   e   R# \        \        4      .p\        P                  P                  R4      R8X  d   VP                  R4        \        P                  ! V\        P                  \        P                  ^ R7      V n         R#   \         d!   p\        RT 24       RT n          Rp?R# Rp?ii ; i)z@Launch the aecmic helper (acquires the mic / VoiceProcessingIO).N
AEC_BYPASS1z--bypassre   stderrbufsizez  [aecmic] spawn failed: )r2  r  r;  r   r   r   r   rc   PopenPIPEDEVNULLr'   r   )ry   argsr)   s   &  r   rA  AECMicStream._spawn  s    ::!J ::>>,'3.KK
#	#))Z__Z5G5GDJ  	-aS12DJJ	s   ;B B?B::B?c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r/    s      t r   c                ~    V P                   Ruqn         Ve    VP                  4        R# R#   \         d     R# i ; i)ux   Terminate the helper — RELEASES the mic device so Bluetooth
earbuds drop the call profile and go back to hi-fi output.N)r2  	terminater'   ry   ps   & r   _killAECMicStream._kill  s?     

D:=   s   - <<c                    V ^8  d   QhRRRR/# r    r   )r   s   "r   r   r/    s      T d r   c                	j    V'       d   V P                  4        V P                  P                  4        R # r1   )rU  r6  r   )ry   r!   s   &&r   r&   AECMicStream._on_mute_change  s     JJL

r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r/    s     &9 &9 &9r   c                	   \         ^,          p\         \        ,          p\        P                  ! \         \        P                  R7      pRpV P
                  P                  4       '       Eg   \        P                  4       '       d[   V P                  4        RpV P                  V4       V P                  P                  V4       V P                  P                  4        K  V P                  pVf@   V P                  4        RpV P                  pVf   V P                  P                  R4       K   VP                  P!                  V4      pT'       g   T P                  4        EK  YF,          p\%        T4      T8  g   EK6  \        P&                  ! TRT \        P(                  R7      pYAR p\        P                  4       '       d   EK  TP+                  \        P                  4      T P,                  R,          ,          pT P                  \        P.                  ! TRR4      4       K  R#   \"         d    T P                  4         EK  i ; i)r	   r   r   Ng?      @r   r   )r  r   r   zerosr   r5  r   r$   rU  r   r6  r   r   r2  rA  re   readr'   len
frombufferint16r   r   r   )	ry   frame_bytes	frame_dursilencebufprocdatapcm16r~   s	   &        r   rB  AECMicStream._reader  s   #a'!K/	((=

;..''))  "" 

g&

	*

  "::D|zz<JJOOC({{''4 

KCc(k)c,;&7rxxH,'$$&&RZZ0DJJ4HIbggeT378C *(  

s   0H- -IIc                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r/  *  s      d r   c                	    \         V J d   R s V P                  P                  4        V P                  P                  4        V P	                  4        R # r1   )r%   r5  r   r6  rU  r&  s   &r   r;   AECMicStream.stop*  s6    $K



r   )r   r2  r5  r3  r6  )r   r   r   r   r   rz   r)  r=  rM   rA  rU  r&   rB  r;   r   r*  r+  s   @r   r-  r-    sJ    > > $ $ &9P r   r-  c                  x   a  ] tR tRtRtR V 3R llt]R R l4       tR R ltR	 R
 lt	R R lt
R R ltRtV ;t# )SystemAudioStreami3  u5  System-audio (loopback) capture via helper/sysaudio (ScreenCaptureKit).

Taps what the Mac is PLAYING — the remote voices of an online meeting come
out of the speakers — cleanly and at full quality. This is the "them" side
of a meeting: the AEC mic deliberately erases speaker output, so the far end
is only audible here. Emits the same 16 kHz int16 mono frames as AECMicStream.

Deliberately simple: NOT part of the mute-hotkey logic (a meeting recording
must not be muted by the mic hotkey) and no AGC (digital audio is already
leveled) — just start/stop and clip to [-1,1]. On SCK failure (no
Screen-Recording grant when spawned from a plain terminal) the helper exits
with a reason on stderr and NO frames arrive; last_error surfaces it so the
caller can tell Ahmed instead of silently recording his side only.
c                   V ^8  d   QhRR/# ro   r   )r   s   "r   r   SystemAudioStream.__annotate__C  s      6 r   c                	   < \         SV `  V4       R V n        R V n        R V n        \
        P                  ! 4       V n        RV n        R # )Nr   )	r   rz   r2  r3  
_errthreadrN   r4  r5  
last_errorr7  s   &&r   rz   SystemAudioStream.__init__C  s:    .2
0437"*r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   rq  L  s     ) )t )r   c                 	*    \         P                  4       # r1   )SYSAUDIO_HELPERr<  r   r   r   r=  SystemAudioStream.availableK  s    &&((r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   rq  O  s        t  r   c                	    \         P                  ! \        \        4      .\         P                  \         P                  ^ R7      V n        \        P                  ! T P                  RR7      T n        T P                  P                  4        \        P                  ! T P                  RR7      T n        T P                  P                  4        R#   \         d1   p\        T4      T n        \        RT 24       RT n         Rp?R# Rp?ii ; i)r8   rH  z  [sysaudio] spawn failed: NTrG   )rc   rK  r  rx  rL  r2  r'   rt  r   rN   rO   rB  r3  rM   
_drain_errrs  )ry   r)   s   & r   rM   SystemAudioStream.startO  s    		#))_%&z!DJ !''t||DI $**$//$O  	!!fDO/s34DJ		s   A	C D
%DD
c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   rq  a  s     
H 
HD 
Hr   c                	D   V P                   pVe   VP                  f   R # \        VP                  P                  R4       F[  pVP	                  RR4      P                  4       pV'       g   K-  RV9  g   K6  VP                  RR4      P                  4       V n        K]  	  R # )Nr   zutf-8replacerunningz	sysaudio:r   )r2  rI  iterreadlinedecoderA   r  rt  )ry   rg  rawrC   s   &   r   r|  SystemAudioStream._drain_erra  sy    zz<4;;.,,c2C::gy1779D$"&,,{B"?"E"E"G 3r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   rq  m  s     9 9 9r   c                	@   \         ^,          pV P                  pVe   VP                  f   R# RpV P                  P	                  4       '       g    VP                  P                  V4      pT'       g   R# Y4,          p\        T4      T8  g   K`  \        P                  ! TRT \        P                  R7      pY1R pTP                  \        P                  4      R,          pT P                  \        P                  ! TRR4      4       K  R#   \         d     R# i ; i)r	   Nr   r\  r]  r   r   )r  r2  re   r5  r   r_  r'   r`  r   ra  rb  r   r   r   r   )ry   rc  rg  rf  rh  ri  r~   s   &      r   rB  SystemAudioStream._readerm  s    #a'zz<4;;...'')){{''4 KCc(k)c,;&7rxxH,'RZZ07:bggeT378 *  s   D DDc                   V ^8  d   QhRR/# r   r   )r   s   "r   r   rq    s      d r   c                	    V P                   P                  4        V P                  R uqn        Ve    VP                  4        R # R #   \         d     R # i ; ir1   )r5  r   r2  rR  r'   rS  s   & r   r;   SystemAudioStream.stop  sM    

D:=   s   A AA)rs  r2  r5  r3  rt  )r   r   r   r   r   rz   r)  r=  rM   r|  rB  r;   r   r*  r+  s   @r   ro  ro  3  s@      ) ) $
H9( r   ro  c                      ] tR tRtRtR R ltR tRR R lltR	 R
 ltR R lt	R R lt
R R lt]R R l4       tRR R lltRtR# )Speakeri  z7Plays queued PCM chunks; stop() silences within ~10 ms.c                   V ^8  d   QhRR/# )r	   r   rX   r   )r   s   "r   r   Speaker.__annotate__  s      3 r   c                	    Wn         \        P                  ! ^ \        P                  R7      V n        \
        P                  ! 4       V n        RV n        RV n	        R# )r8   r\  NF)
r   r   r^  r   _bufferrN   Lock_lockr   _active)ry   r   s   &&r   rz   Speaker.__init__  s9    $xx4^^%
/3r   c                	L   V P                   ;_uu_ 4        \        V\        V P                  4      4      pV P                  R V VR V1^ 3&   RWR 1^ 3&   V P                  VR  V n        \        V P                  4      ^ 8X  d   RV n        R R R 4       R #   + '       g   i     R # ; i)Nr   F)r  r   r`  r  r  )ry   outdatarx   r  _statusr   s   &&&&& r   r  Speaker._callback  s    ZZZFC-.A!\\"1-GBQBEN GBEN<<+DL4<< A%$ ZZZs   A3BB#	Nc                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r    s     
 
D 
r   c           
     	    \         P                  ! V P                  ^R^ RVV P                  R7      V n        V P                  P                  4        R# )   r   low)r   r   r   r
  latencyr   r  N)r   OutputStreamr   r  r   rM   )ry   r   s   &&r   _openSpeaker._open  sA    ^^
 	r   c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r    s     * *t *r   c                z   ^ RI p V P                  4        R#   \         d*   p\        R\	        T4      R,           R24        Rp?MRp?ii ; iTP
                  R8X  d    ^ RIpTP                  R4      ;'       g    Rp\        P                  P                  T4      '       d   \        P                  ! TRR	R
.RR^R7      P                  P                  4       p\        R T 4       R4      pT'       d.   \        P                  ! TR	R
RT.R^R7       \        RT R24       M  \         d     Mi ; i\!        ^4       Fw  p T P                  4         R#   \         dU   pT^8X  d.   \        R\	        T4      R,           R24       RT n         Rp? R# ^ RIpTP'                  R4        Rp?Kq  Rp?ii ; i	  R# )uN  Open the output stream. The default output can be a flaky Bluetooth
device (e.g. disconnected CMF Buds) that fails with a PortAudio error —
that used to CRASH the whole engine at startup. So on failure we switch
the system default output to the built-in speakers and retry, and if
even that fails we degrade to mute rather than die.Nz"  speaker: default output failed (:N<   Nu"   ) — trying the built-in speakersrZ   SwitchAudioSourcez#/opt/homebrew/bin/SwitchAudioSourcez-az-toutputTr]   c              3     "   T FA  pR VP                  4       9   g   RVP                  4       9   g   K/  VP                  4       x  KC  	  R# 5i)macbookzbuilt-inN)rB   rA   )r   os   & r   r    Speaker.start.<locals>.<genexpr>  s<      $@t!'0AGGI'='1QWWY'> %.AGGIIts
   ,AAz-sra   z  speaker: switched output to ''z&  speaker: could not open any output (u6   ) — Jarvis will be MUTE until an output device worksg      ?)r?   r  r'   r   r  rb   shutilwhichr   pathexistsrc   rd   re   
splitlinesnextranger   timesleep)	ry   r?   r)   r  sasallobuiltinattempt_ts	   &        r   rM   Speaker.start  s    		2JJL 	26s1vc{m D1 1 2 2	2 <<8#||$78 @ @? 77>>#&&%>>3dH*E9=D2355;VJJL  # $@t $@AEGG "T8T7'K6:AG ?yJK QxG


 a<B3q6#;- PR R S#'DL!	  sQ    AAA!D$ ='D$ %A>D$ $D21D2EF6"-F1F11F6c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r    s        T  r   c                	    V P                   e>   V P                   P                  4        V P                   P                  4        R V n         R # R # r1   r%  r&  s   &r   stop_streamSpeaker.stop_stream  r(  r   c                    V ^8  d   QhRRRR/# )r	   pcmr   r
   r   r   )r   s   "r   r   r    s        
  t  r   c                    V P                   ;_uu_ 4        \        P                  ! V P                  VP	                  \        P
                  4      .4      V n        RV n        RRR4       R#   + '       g   i     R# ; i)z.Queue audio (float32 mono at self.samplerate).TN)r  r   concatenater  r   r   r  )ry   r  s   &&r   playSpeaker.play  sD    ZZZ>>4<<BJJ9O*PQDLDL ZZZs   AA++A<	c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r    s     ! !T !r   c                    V P                   ;_uu_ 4        \        P                  ! ^ \        P                  R7      V n        RV n        RRR4       R#   + '       g   i     R# ; i)z6Barge-in: drop everything queued, silence immediately.r\  FN)r  r   r^  r   r  r  r&  s   &r   cutSpeaker.cut  s4    ZZZ88ARZZ8DL DL ZZZs   3AA#	c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r    s      $ r   c                	    V P                   # r1   )r  r&  s   &r   speakingSpeaker.speaking  s    ||r   c               $    V ^8  d   QhRRRRRR/# )r	   pollr   r`   r
   r   r   )r   s   "r   r   r    s!      E 5 T r   c                   "   RpV P                   '       dL   \        P                  ! V4      G Rj  xL
  W1,          pW28  g   K@  V P                  4        \	        R4       R# R#  L35i)zWait for playback to drain. The timeout is a fail-safe: if the
output device wedges (never drains), cut instead of hanging the
whole conversation loop in 'speaking' forever.r   Nu7          [speaker: playback wedged — cut after timeout])r  rv   r  r  r   )ry   r  r`   waiteds   &&& r   	wait_doneSpeaker.wait_done  sQ      mmm--%%%NF 
OP %s   A&A&A$A& A&)r  r  r  r   r   r1   )g?g      ^@)r   r   r   r   r   rz   r  r  rM   r  r  r  propertyr  r  r   r   r   r   r  r    sG    A%
*X  !   r   r  )F   )g333333?)3r   
__future__r   rv   r   rc   rN   pathlibr   numpyr   sounddevicer   pynputr   rK   rJ   ImportErrorr4  r$   r   r%   r  r#   r   r   r-   r   r   r   r*   r.   r2   rR   rU   rh   r   r  __file__resolveparentr;  rx  rm   r   r   r   r   r-  ro  r  r   r   r   <module>r     sz   #  	     &J __
		^^
jjnnZ)9: zz~~k+;<E=E'B. (^##%,,33h>I
x.((*11888CjP 24n,^	U  U ps; slU Upl lg  Js   F 	FF