+
    ވSj                        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
]! ]4      P                  4       P                  P                  R,          t]P                  P!                  RR4      t]P                  P!                  R4      ;'       g    Rt]RR	.t ! R
 R4      t ! R R4      t ! R R4      tR R ltR# )u  Meeting mode — Jarvis records, transcribes live, and takes notes silently.

Ahmed triggers it ("record this meeting" / "record this online meeting"). While
active, Jarvis DOES NOT reply to what's said — he just transcribes every
utterance into a growing transcript and stays quiet, catching up on context.
He only speaks/answers when Ahmed ADDRESSES him ("Jarvis, …") or types a
question — and answering never pauses the recording (capture continues on the
mic while he thinks). When Ahmed ends it, the full transcript is saved and
summarised into the long-term memory brain, linked to the meeting.

  face-to-face : microphone (the room)
  online       : mic (Ahmed, via AEC) + the laptop's own audio (the remote
                 participants), captured natively with ScreenCaptureKit on a
                 SEPARATE channel — see SystemAudioStream in voice/audio_io.py
                 and _capture_remote in main.py (env flag MEETING_SYSAUDIO).
                 Ahmed's mic is labelled "Ahmed"; the call audio is "them".

Transcription (BOTH modes) goes through multilingual auto-detect Whisper
(MeetingSTT below) so real Saudi/Gulf code-switching — Arabic + English in the
SAME sentence — is decoded natively, instead of the English-only Parakeet the
main loop uses. It runs on a background worker (live where it keeps up, backlog-
tolerant when it can't) and the RAW audio is always written to WAV alongside the
transcript, so a bad/slow transcript can be re-run and nothing is ever lost.

This module holds the session + transcript + the STT/WAV helpers; main.py drives
capture and decides silence-vs-answer. Recordings are written to recordings/.
)annotationsN)Path
recordingsMEETING_STT_MODELz"mlx-community/whisper-large-v3-mlxMEETING_STT_LANGz$mlx-community/whisper-large-v3-turbozmlx-community/whisper-small-mlxc                      ] tR t^6tRR R lltRR R lltR R lt]R R	 l4       tRR
 R llt	R R lt
R R ltR R ltR R ltRtR# )MeetingSessionc               $    V ^8  d   QhRRRRRR/# )   modestrtitlereturnNone )formats   "voice/meeting.py__annotate__MeetingSession.__annotate__7   s!     , ,S ,s ,D ,    c                	   Wn         T;'       g    R V n        \        P                  ! 4       V n        . V n        RP                  R V P                   4       4      R,          P                  4       P                  RR4      pR\        P                  ! R\        P                  ! V P                  4      4       RT;'       g    R 2V n
        R	# )
meeting c              3  d   "   T F&  pVP                  4       '       g   VR 9   d   TMRx  K(  	  R# 5i)z -_r   N)isalnum).0cs   & r   	<genexpr>*MeetingSession.__init__.<locals>.<genexpr>>   s,      , *1 IIKK1:q2= *s   00:N(   N _meeting_z%Y%m%d_%H%MnotesN)r   r   timestartedlinesjoinstripreplacestrftime	localtimestem)selfr   r   safes   &&& r   __init__MeetingSession.__init__7   s    	''i
yy{ 46
ww , $

, ,,/116c9J 	  mT^^DLL=Y Z[)+	r   Nc               (    V ^8  d   QhRRRRRRRR/# )r
   speakerr   texttszfloat | Noner   r   r   )r   s   "r   r   r   D   s.     V V3 Vc V| Vt Vr   c                	    T;'       g    R P                  4       pV'       d9   V P                  P                  Ve   TM\        P                  ! 4       W34       R# R# )r   N)r(   r&   appendr$   )r-   r2   r3   r4   s   &&&&r   addMeetingSession.addD   sA    

!!#JJR^rgTU r   c                   V ^8  d   QhRR/# )r
   r   zlist[tuple[float, str, str]]r   )r   s   "r   r   r   I   s     6 66 6r   c                	2    \        V P                  R  R7      # )c                    V ^ ,          # )    r   )xs   &r   <lambda>)MeetingSession._ordered.<locals>.<lambda>J   s    !r   )key)sortedr&   r-   s   &r   _orderedMeetingSession._orderedI   s    djjn55r   c                   V ^8  d   QhRR/# r
   r   floatr   )r   s   "r   r   r   M   s     3 3 3r   c                	\    \         P                   ! 4       V P                  ,
          R ,          # )g      N@)r$   r%   rB   s   &r   minutesMeetingSession.minutesL   s    		dll*d22r   c                    V ^8  d   QhRRRR/# )r
   nintr   r   r   )r   s   "r   r   r   P   s     O O OS Or   c                V    RP                  R V P                  4       V) R  4       4      # )ziThe tail of the transcript, for answering a mid-meeting question
with full context of what was just said.
c              3  4   "   T F  w  rq2 R V 2x  K  	  R# 5i): Nr   )r   _tsptxs   &   r   r   (MeetingSession.recent.<locals>.<genexpr>S   s     N9M:224r"9Ms   N)r'   rC   )r-   rL   s   &&r   recentMeetingSession.recentP   s(     yyN!9MNNNr   c                   V ^8  d   QhRR/# r
   r   r   r   )r   s   "r   r   r   U   s      3 r   c                	    R V P                    RV P                   R\        P                  ! R4       R2R.pV P	                  4        F  w  r#pVP                  V RV 24       K  	  RP                  V4      # )z# z  (z, z%Y-%m-%d %H:%M)r   rQ   rO   )r   r   r$   r*   rC   r6   r'   )r-   outrR   rS   rT   s   &    r   	full_textMeetingSession.full_textU   sr    DJJ<s499+R-- 012!568:--/JBBJJ"Rt}% *yy~r   c                   V ^8  d   QhRR/# r
   r   r   r   )r   s   "r   r   r   \        . .t .r   c                6    \         V P                   R2,          # )z$Mic/room channel (Ahmed + the room).z	_room.wav_RECr,   rB   s   &r   room_wav_pathMeetingSession.room_wav_path\       9---r   c                   V ^8  d   QhRR/# r`   r   )r   s   "r   r   r   `   ra   r   c                6    \         V P                   R2,          # )u?   System-audio channel (the remote participants) — online only.z	_them.wavrc   rB   s   &r   them_wav_pathMeetingSession.them_wav_path`   rg   r   c                   V ^8  d   QhRR/# rY   r   )r   s   "r   r   r   d   s      c r   c                    \         P                  RR7       \         V P                   R2,          pVP                  V P	                  4       RR7       \        V4      # )z=Write the transcript to recordings/ and return the file path.Texist_okz.mdzutf-8)encoding)rd   mkdirr,   
write_textr]   r   r-   paths   & r   saveMeetingSession.saved   sF    

D
!3''(7;4yr   )r&   r   r%   r,   r   )	in_personr   N)r   )__name__
__module____qualname____firstlineno__r/   r7   rC   propertyrI   rV   r]   re   rj   ru   __static_attributes__r   r   r   r   r   6   sE    ,V
6 3 3O
.. r   r   c                  T    ] tR t^ltRtR R ltR R ltR R lt]R R	 l4       t	R
t
R# )WavRecorderu'  Incremental 16 kHz mono int16 WAV writer (stdlib `wave` → a proper RIFF
header). Writes to disk as audio arrives (low RAM; a crash leaves only a
slightly-wrong length field, data is still recoverable). This is the raw-
audio safety net: every meeting is re-transcribable even if live STT lags.c                   V ^8  d   QhRR/# r
   r   r   r   )r   s   "r   r   WavRecorder.__annotate__r   s     & & &r   c                	   \        V4      V n        \        P                  R R7       \        P
                  ! V P                  R4      V n        V P                  P                  ^4       V P                  P                  ^4       V P                  P                  R4       ^ V n
        \        P                  ! 4       V n        R# )Trn   wb>  N)r   rt   rd   rq   waveopen_wfsetnchannelssetsampwidthsetframeratesamples	threadingLock_lockrs   s   &&r   r/   WavRecorder.__init__r   s|    I	

D
!+/99TYY+Ea a e$^^%
r   c                    V ^8  d   QhRRRR/# )r
   audio
np.ndarrayr   r   r   )r   s   "r   r   r   |   s     ' ': '$ 'r   c                	   Ve   \        V4      ^ 8X  d   R # \        P                  ! VRR4      R,          P                  R4      pV P                  ;_uu_ 4        V P
                  f    R R R 4       R # V P
                  P                  VP                  4       4       V ;P                  \        V4      ,          un        R R R 4       R #   + '       g   i     R # ; i)Ng      ?g    @z<i2g      )	lennpclipastyper   r   writeframestobytesr   )r-   r   pcm16s   && r   writeWavRecorder.write|   s    =CJ!Oc*W4<<UCZZZxx Z HH  1LLCJ&L	 ZZZs   C3ACC	c                   V ^8  d   QhRR/# r   r   )r   s   "r   r   r      s        t  r   c                	    V P                   ;_uu_ 4        V P                  e#    V P                  P                  4        R V n        R R R 4       R #   \         d     Li ; i  + '       g   i     R # ; irx   )r   r   close	ExceptionrB   s   &r   r   WavRecorder.close   sV    ZZZxx#HHNN$   Z ! 	 ZZs-   A!AA!AA!AA!!A2	c                   V ^8  d   QhRR/# rF   r   )r   s   "r   r   r      s     & & &r   c                	(    V P                   R ,          # )g     @@)r   rB   s   &r   secondsWavRecorder.seconds   s    ||g%%r   )r   r   rt   r   N)ry   rz   r{   r|   __doc__r/   r   r   r}   r   r~   r   r   r   r   r   l   s+    R
&'  & &r   r   c                  2    ] tR t^tRtR R ltR R ltRtR# )
MeetingSTTuk  Multilingual auto-detect Whisper (mlx-whisper) for meeting transcription.

language=None → Whisper detects per-utterance and keeps Arabic+English
code-switching inline (proven on Ahmed's live audio). All MLX inference is
pinned to voice.stt._MLX — the single Metal-affine thread — so it serialises
with (and never corrupts) the main loop's Parakeet stream.
c                    V ^8  d   QhRRRR/# )r
   repor   r   r   r   )r   s   "r   r   MeetingSTT.__annotate__   s     $ $S $T $r   c                	   a a SS n         ^ RIHp VS n        R VV 3R llpVP                  V4      P	                  4        R# )r<   )_MLXc                   V ^8  d   QhRR/# r   r   )r   s   "r   r   )MeetingSTT.__init__.<locals>.__annotate__   s     	A 	At 	Ar   c                    < ^ RI p V Sn        SP                  P                  \        P                  ! R\        P
                  R7      S\        R7       R# )r<   Nr   dtypepath_or_hf_repolanguage)mlx_whisper_whisper
transcriber   zerosfloat32r   )r   r   r-   s    r   _load"MeetingSTT.__init__.<locals>._load   s?    'DM MM$$bjj1 $/? % Ar   N)r   	voice.sttr   submitresult)r-   r   r   r   s   ff  r   r/   MeetingSTT.__init__   s7    	"		A 	A 	E!!#r   c                    V ^8  d   QhRRRR/# )r
   r   r   r   r   r   )r   s   "r   r   r      s     . .
 .s .r   c                l   a a R VV 3R llpS P                   P                  V4      P                  4       # )z=audio: 1-D float32 mono @ 16 kHz -> code-switched transcript.c                   V ^8  d   QhRR/# rY   r   )r   s   "r   r   +MeetingSTT.transcribe.<locals>.__annotate__   s     	3 	3S 	3r   c                    < SP                   P                  \        P                  ! S\        P                  R 7      SP
                  \        R7      p V P                  R4      ;'       g    RP                  4       # )r   r   r3   r   )	r   r   r   ascontiguousarrayr   r   r   getr(   )r\   r   r-   s    r   _do"MeetingSTT.transcribe.<locals>._do   s\    --**$$U"**= $		4D + FC GGFO))r0022r   )r   r   r   )r-   r   r   s   ff r   r   MeetingSTT.transcribe   s-    	3 	3
 yy$++--r   )r   r   r   N)ry   rz   r{   r|   r   r/   r   r~   r   r   r   r   r      s    $. .r   r   c                   V ^8  d   QhRR/# )r
   r   ztuple[MeetingSTT, str]r   )r   s   "r   r   r      s     = =0 =r   c            	     
   Rp \        4       p\         F3  pV'       d   W!9   d   K  VP                  V4        \        V4      pW23u # 	  \        RV  24      h  \         d    pTp \        RT RT R24        Rp?Kj  Rp?ii ; i)u   Load the multilingual meeting STT, degrading through the fallback chain.
Returns (stt, model_repo). Raises only if EVERY option fails (then main.py
falls back to the main-loop STT). Blocking — call via asyncio.to_thread.Nz  [meeting-stt] z unavailable (z); trying nextzno meeting STT could load: )set_STT_FALLBACKSr7   r   r   printRuntimeError)lasttriedr   sttes        r   load_meeting_sttr      s     "DeEt}		$	LT"C9  4TF;
<<  	LD$TF.>JKK	Ls   AB#A==B)r   
__future__r   osr   r$   r   pathlibr   numpyr   __file__resolveparentrd   environr   r   r   r   r   r   r   r   r   r   r   <module>r      s   6 # 	     H~&&--< JJNN=? ::>>"45==  *%3 3l%& %&P. .D=r   