
    xj&              
      Z   U d Z ddlmZ ddlZddlZddlZddlZddlZddlm	Z	 ddl
mZmZmZmZ  ej        e          ZdZdZdZd	Zd
Zd
Z G d de          Zdddddddedd	Zded<   dZ eh d          ZdydzdZd{dZ d|dZ!da"d}d!Z#d{d"Z$d~d#Z%dd&Z&d}d'Z'dd)Z(dd+Z)dd-Z*ddd/Z+	 dddd0dd3Z,d{d4Z-ddd6Z.d|d7Z/dd9Z0d: Z1d{d;Z2dd=Z3dd?Z4dd@Z5ddBZ6ddCZ7 G dD dE          Z8ddGZ9 G dH dIe8          Z:dJZ;dKZ<ddMZ=dddPZ> G dQ dRe8          Z? G dS dTe8          Z@ddUZAd{dVZBd{dWZCdddXZD G dY dZ          ZEdaFd[ed\<   daGd]ed^<   daH ejI                    ZJdd_ZKdddaZLddcZMddeZNdddfddmZOddnZPddoZQddpZRddqZSd{drZTd{dsZUdddtZVddvZWddwZXd~dxZYdS )u  Wake-word ("Hey Hermes") detection — hands-free session trigger.

A lightweight, always-on hotword listener that fires a callback when a wake
phrase is spoken — the "Hey Siri" / "Alexa" pattern. Shared by the CLI, TUI, and
desktop GUI (one of them owns it, gated by ``wake_surface_enabled``): say the
wake word, Hermes opens a fresh session and captures voice via the existing
pipeline, then answers.

Three engines, all fully on-device (no audio leaves the machine for detection):

* **openwakeword** (default, free, no API key) — loads an ONNX model. Defaults
  to the bundled "hey hermes" model (``tools/wakewords/``) so the wake word
  works out of the box; or point ``wake_word.openwakeword.model`` at a built-in
  name (``hey_jarvis``, ``alexa``, …) or a custom ``.onnx`` for another phrase.
* **sherpa** (free, no API key, open vocabulary) — sherpa-onnx keyword
  spotting. Detects ANY typed phrase with no training: set
  ``wake_word.phrase`` and the phrase is tokenized at runtime against a small
  streaming zipformer model (~13 MB English model, one-time download).
* **porcupine** (premium) — Picovoice's engine. Needs ``PORCUPINE_ACCESS_KEY``;
  supports built-in keywords and custom ``.ppn`` files from the Picovoice
  Console.

Audio capture reuses the same 16 kHz mono int16 ``sounddevice`` path as voice
mode. The detector runs on its own daemon thread; callers ``pause()`` it while a
voice turn holds the microphone and ``resume()`` it once the system is idle
again (two input streams on one device is unreliable cross-platform).

Nothing here mutates agent context or the prompt cache — on wake we hand a plain
string to the caller, exactly like a voice transcript.
    )annotationsN)Path)AnyCallableDictOptionali>         @g      @   
   c                      e Zd ZdZdS )WakeWordInUsezCRaised when another surface or process owns the wake-word listener.N)__name__
__module____qualname____doc__     3/home/agent/.hermes/hermes-agent/tools/wake_word.pyr   r   C   s        MMMMr   r   Fautoopenwakeword
hey hermesg333333?T)	enabledsurfaceinput_devicecaptureproviderphrasesensitivityconfirmation_framesstart_new_sessionDict[str, Any]	_DEFAULTS
hey_hermes>    hermesr#   r   onnx	frameworkstrreturnc                   t          |                                                                           dk    rdnd}t          j                            t          j                            t                    dt           d|           S )zGPath to the shipped hey_hermes model (.onnx/.tflite) for ``framework``.tfliter&   	wakewords.)	r(   striplowerospathjoindirname__file___BUNDLED_MODEL_NAME)r'   exts     r   _bundled_wakeword_pathr7   a   si    )nn**,,2244@@((fC7<<11;CV@^@^Y\@^@^___r   boolc                 Z    dd l } t          j         dk    o|                                 dk    S )Nr   darwinarm64)platformsysmachine)r<   s    r   _is_macos_arm64r?   g   s.    OOO<8#E(8(8(:(:g(EEr   c                 &    t                      rdndS )u  The openWakeWord backend to use on this platform.

    openWakeWord's ONNX backend produces near-zero scores on macOS ARM64 — its
    shared *embedding* model is the broken stage (the melspectrogram front-end
    and the wake classifier both match tflite exactly). The detector arms, the
    microphone works, and no phrase can ever cross the threshold. Prefer the
    tflite backend there; ONNX stays the default everywhere else.

    Upstream: https://github.com/dscripka/openWakeWord/issues/336
    r+   r&   )r?   r   r   r   default_inference_frameworkrA   m   s     '((488f4r   cfgc                   t          |                     d          t                    r|                     d          ni }t          |                    d          pd                                                                          }|st                      S |dk    r3t                      r%t          sdat          
                    d           dS |S )u  Resolve the effective openWakeWord backend from config.

    Honors an explicit ``openwakeword.inference_framework`` — EXCEPT the one
    combination that is provably dead: an explicit ``onnx`` on macOS ARM64,
    where ONNX's embedding model never lets a phrase cross threshold (upstream
    #336). Existing macOS users who pinned ``onnx`` before the tflite fix landed
    would otherwise keep a wake word that arms but never fires. Coerce that one
    case to tflite (with a one-time warning) instead of silently shipping a dead
    ear. Every other explicit value is respected as-is; empty falls back to the
    platform default.
    r   inference_frameworkr$   r&   Tu   wake: openwakeword.inference_framework='onnx' is set but ONNX's embedding model never fires on macOS ARM64 (openWakeWord #336) — using tflite instead. Set inference_framework to '' (auto) or 'tflite' in config.yaml to silence this.r+   )
isinstancegetdictr(   r.   r/   rA   r?   _warned_onnx_coercedloggerwarning)rB   subr'   s      r   resolve_inference_frameworkrL   ~   s     &00G0G%N%N
V#''.
!
!
!TVCCGG1228b99??AAGGIII -*,,,F00# 	#' NN;   xr   c                 8   	 ddl } dS # t          $ r Y nw xY w	 ddlm} n# t          $ r Y dS w xY wddl}|                    d          }g |_        t          j        	                    d|           |t          j        d<   t                              d           dS )	us  Make ``import tflite_runtime.interpreter`` resolve, returning success.

    openWakeWord hardcodes that import but only declares ``tflite-runtime`` for
    ``platform_system == "Linux"``; on macOS the equivalent wheel is
    ``ai-edge-litert``. Alias the module so the upstream import succeeds. The
    alias is process-local — nothing is written to site-packages.
    r   NT)interpreterFtflite_runtimeztflite_runtime.interpreterz3wake word: bridged tflite_runtime -> ai_edge_litert)tflite_runtime.interpreterImportErrorai_edge_litertrN   types
ModuleType__path__r=   modules
setdefaultrI   debug)rO   _litertrS   pkgs       r   ensure_tflite_runtimer[      s    ))))t   9999999   uu LLL


+
,
,CCLK+S11107CK,-
LLFGGG4s    
  
..c                     	 ddl m}   |                                 d          }n# t          $ r d}Y nw xY wt	          |t
                    r|ni S )zAReturn the ``wake_word`` config section, shape-guarded to a dict.r   )load_config	wake_wordN)hermes_cli.configr]   rF   	ExceptionrE   rG   )r]   rB   s     r   load_wake_word_configra      sq    111111kmm,,   S$''/33R/s   #& 55keyr   c                    |                      |t                               |                    }|t                               |          n|S N)rF   r"   )rB   rb   vals      r   _getrf      s<    
''#y}}S))
*
*C!$9==#5r   c                    t          t          | d                                                                                    pdS )Nr   r   )r(   rf   r.   r/   rB   s    r   	_providerri      s7    tC$$%%++--3355GGr   int | str | Nonec                    t          | d          }|t          |t                    rdS t          |t                    r|S t	          |                                          }|pdS )zBConfigured PortAudio input selector, preserving indices and names.r   N)rf   rE   r8   intr(   r.   )rB   rawvalues      r   _input_devicero      s_    
sN
#
#C
{jd++{t#s 
HHNNE=Dr   floatc                    t          | d          }	 t          |          }n1# t          t          f$ r t          t          d                   }Y nw xY wt          t          |d          d          S )Nr                 ?)rf   rp   	TypeError
ValueErrorr"   minmax)rB   rm   ss      r   _sensitivityry      sq    
sM
"
"C,#JJz" , , ,)M*++,s1c{{C   s   " +AArl   c                    t          | d          }	 t          |          }n# t          t          f$ r
 t          }Y nw xY wt          t          |d          d          S )zHow many consecutive over-threshold frames are required to fire.

    ``1`` restores the old single-frame behaviour; higher values reject
    ambient-speech blips at the cost of a few tens of ms of extra latency.
    Clamped to a sane 1..10.
    r      r   )rf   rl   rt   ru   _DEFAULT_CONFIRMATION_FRAMESrv   rw   )rB   rm   ns      r   _confirmation_framesr~      sg     s)
*
*C)HHz" ) ) )()s1ayy"s   " ==Optional[Dict[str, Any]]c                d    | | nt                      } t          t          | d                    pdS )zHHuman-facing wake phrase label (purely cosmetic; engine keys detection).Nr   r   )ra   r(   rf   rh   s    r   wake_phraser      s3    ##&;&=&=CtC""##3|3r   )prefer_clientforce_localr   r   c                   | | nt                      } |rdS t          t          | d          pd                                                                          }|dv rdS |dk    rdS t                      rdS |rdS dS )zReturn ``local`` or ``client`` capture mode for this arm.

    ``prefer_client`` is set by remote desktop (Mac mic, headless backend).
    ``force_local`` keeps CLI/TUI on the process mic. Config ``capture`` is
    ``auto`` | ``local`` | ``client``.
    Nlocalr   r   )clientremoteexternalr   )ra   r(   rf   r.   r/   _local_input_device_ready)rB   r   r   rm   s       r   resolve_capture_moder      s     ##&;&=&=C w
d3	"",f
-
-
3
3
5
5
;
;
=
=C
...x
g~~w
 !"" w x 7r   c                    	 t                      \  } }n# t          t          f$ r Y dS w xY w	 |                                 }n# t          $ r Y dS w xY wt          |t                    r(t          |                    d          pd          dk    S 	 |D ]Y}t          |t                    r|                    d          nd}|t          |dd          }t          |pd          dk    r dS Zn# t          $ r Y dS w xY w	 |                     dd          }t          |t                    r|                    d          nd}t          |pd          dk    S # t          $ r Y dS w xY w)zGTrue when PortAudio is importable and at least one input device exists.Fmax_input_channelsr   NTinput)
_import_audiorQ   OSErrorquery_devicesr`   rE   rG   rl   rF   getattr)sd_devicesdevchannelsinfos         r   r   r     s   AA!   uu""$$   uu'4   ?7;;3449::Q>> 	 	C8B38M8MWsww3444SWH"3(<a@@8=q!!A%%tt &		    uug..5?d5K5KR4880111QR8=q!!A%%   uusI    ))A 
AAAC. ,C. .
C<;C< AE 
E%$E%r   c                :   ||nt                      }|                    d          sdS t          t          |d                                                                                    pd}|dk    p)||                                                                 k    S )a/  Should ``surface`` (``cli`` / ``tui`` / ``gui``) host the listener?

    True when the wake word is enabled and the configured ``surface`` is either
    ``auto`` or this exact surface.  ``auto`` makes a surface eligible; the
    process/machine ownership lock still permits only the first claimant.
    Nr   Fr   r   )ra   rF   r(   rf   r.   r/   )r   rB   wants      r   wake_surface_enabledr   <  s     ##&;&=&=C779 utC##$$**,,2244>D6><TW]]__%:%:%<%<<<r   c                 J    	 ddl m}   |             pdS # t          $ r Y dS w xY w)Nr   get_active_profile_namedefault)hermes_cli.profilesr   r`   r   s    r   _active_profile_namer   N  sO    ??????&&((5I5   yys    
""Dict[str, str]c                    i } 	 ddl m} ddlm}m}  |            D ]}t          |dd          pt          |          }	 t           ||                    dz  } ||          }|                    d          pi }t          |t                    r|                    d          st          |                    d	          pd
|                                           }	|	r|	| |<   # t          $ r Y w xY wn# t          $ r Y nw xY w| S )a  Map ``profile name -> wake phrase`` for every wake-enabled profile.

    Reads each profile's own ``config.yaml`` raw (cheap, no full config merge).
    A profile is enrolled when its ``wake_word.enabled`` is truthy; its phrase
    defaults to ``"hey <profile>"`` when unset. Used by the sherpa engine to
    listen for every enrolled profile's phrase at once and route the wake to
    the matching profile. Best-effort: unreadable profiles are skipped.
    r   )read_user_config_raw)get_profile_dirlist_profilesnameNzconfig.yamlr^   r   r   zhey )r_   r   r   r   r   r   r(   r   rF   rE   rG   r.   r`   )
phrasesr   r   r   r   r   cfg_pathrm   wcr   s
             r   enrolled_profile_phrasesr   W  sh    !G::::::FFFFFFFF!MOO 	 	D4..;#d))D   5 566F**844WW[))/R!"d++ 266)3D3D RVVH-->??EEGG +$*GDM   	    NsB   :C9 A'C(&C9 'A C('C9 (
C52C9 4C55C9 9
DDc                     dd l } dd l}|| fS Nr   )numpysounddevice)npr   s     r   r   r   }  s#    r6Mr   c                 T    	 t                       dS # t          t          f$ r Y dS w xY w)NTF)r   rQ   r   r   r   r   _audio_availabler     s<    t!   uus    ''selectorc                   d|i}	 |                      |d          }n+# t          $ r}t          |          |d<   |cY d}~S d}~ww xY wt          |t                    rf|                    d          }|rt          |          |d<   |                    d          }t          |t          t          f          rt          |          |d<   |                    d          }t          |t          t          f          rt          |          |d<   |                    d          }t          |t          t          f          rt          |          |d	<   	 |                     t          |                    }	t          |	t                    r|	                    d          nd}
|
rt          |
          |d<   n# t          $ r Y nw xY w|S )
zResolve a PortAudio selector into JSON-safe diagnostics.

    Device discovery is diagnostic only. ``InputStream`` remains the authority
    on whether the selected device can actually open at the requested format.
    r   r   errorNr   r   default_sampleratehostapihostapi_index)	r   r`   r(   rE   rG   rF   rl   rp   query_hostapis)r   r   detailsr   er   r   rater   r   hostapi_names              r   _describe_input_devicer     s     *84G'22   q66 $ xx 	(!$iiGFO88011he-- 	:,/MMG()xx,--dS%L)) 	8,1$KKG()++mc5\22 	'*='9'9GO$++C,>,>??6@$6O6OYw{{6222UY ;),\):):GI&    Ns,    
AA A AA"F4 4
G Gr   c                F   t          |                     d          pd                                          }|                     d          }|p|dnt          |          }t          |                     d          pd                                          }|r| d| dn|S )Nr   r$   r   system defaultr   z ())r(   rF   r.   )r   r   r   labelr   s        r   _device_labelr     s    w{{6""(b))//11D{{:&&HM)9%%s8}}E'++i((.B//5577G%,7e!!w!!!!%7r   c                   |                      d          }t          |t          t          f          rOt          |t                    s:|dk    r4	 t          t          |                    S # t          t          f$ r Y nw xY wt          S )zAUse the selected device's native rate when PortAudio reports one.r   r   )	rF   rE   rl   rp   r8   roundOverflowErrorru   SAMPLE_RATE)r   r   s     r   _capture_sample_rater     s    ;;+,,D$e%% jt.D.D PQ	uT{{###z* 	 	 	D	s   A* *A>=A>output_lengthc                (   |                      || j                                      d          }|j        |k    r/|                      || j                                      d          S |j        dk    r|                     || j                  S |j        |k    r`|                     d|j        |dz   | j                  }| j        	                    ||dd                   | 
                    |          z  }nW|                     |j        | j                  }|                     d|j        dz
  |          }|                     |||          }|                     |                              dd                              | j                  S )z?Convert one native-rate capture block to an exact engine frame.dtyper   r{   Ni i  )asarrayfloat64reshapesizeint16zeroslinspaceint64addreduceatdiffarangeinterprintclipastype)r   framer   sourceedgesvaluessource_positionstarget_positionss           r   _resample_audio_framer     s]   ZZRZZ0088<<F{m##zz%rxz0088<<<{axxRXx666{]"" Av{MA,=RXNNss44rwwu~~E 99V[
9CC;;q&+/=II+-=vFF776??..55bh???r   c                    t           j        dk    r	 dS t           j        dk    rdt          |            dS dt          |            dS )zEPlatform-specific remediation for an armed stream delivering silence.r:   zMicrophone delivers only silence. Grant the Hermes backend microphone access in System Settings > Privacy & Security > Microphone, then toggle the wake word.win32z&Microphone delivers only silence from z^. Set wake_word.input_device to a different PortAudio input device, then toggle the wake word.z=. Check the selected input device, then toggle the wake word.)r=   r<   r   )r   s    r   silent_audio_hintr     sw    
|x5	
 	

 |w)]75K5K ) ) )	
	Fw1G1G 	F 	F 	Fr   c                  H    e Zd ZU dZdZded<   dZded<   dd
ZddZddZ	dS )_Enginez?Minimal hotword-engine contract: feed int16 frames, get a bool.   rl   frame_lengthNOptional[tuple[str, str]]
last_matchr)   r8   c                    t           rd   )NotImplementedErrorselfr   s     r   processz_Engine.process  s    !!r   Nonec                    dS )zDClear any internal audio/feature buffer (called on every (re)start).Nr   r   s    r   resetz_Engine.reset  s    r   c                    d S rd   r   r   s    r   closez_Engine.close  s    r   r)   r8   r)   r   )
r   r   r   r   r   __annotations__r   r   r   r   r   r   r   r   r     s|         IIL -1J0000" " " "        r   r   rn   c                    t           j        | v p3|                     d          pt           j                            |           S )N)z.onnxz.tflitez.ppn)r0   sependswithr1   exists)rn   s    r   _looks_like_pathr   	  s;    
% 	!>>677	!7>>%  r   c                  6    e Zd ZdZdZddZddZdd
ZddZdS )_OpenWakeWordEngineu4   openWakeWord — free, local ONNX hotword detection.r   rB   r!   c                   ddl m} |                    dd           dd l}ddlm} t          |                    d          t                    r|                    d          ni }t          |                    d          pt                                                    }t          |          }t          |          | _        t          |          | _        d| _        |d	k    rt%                      s	 |                    d
d           n2# t&          $ r%}t(                              d|           Y d }~nd }~ww xY wt%                      s9t-                      rt/          d          t(                              d           d}|                                t4          v rt7          |          }	 |j                            |g           n2# t&          $ r%}t(                              d|           Y d }~nd }~ww xY w|g}	 ||	|          | _        t?          | j        j         !                                          | _"        d S )Nr   	lazy_depswake.openwakewordFprompt)Modelr   modelr+   wake.openwakeword.tflitez,wake word: tflite runtime install failed: %sz{The wake word needs the tflite backend on this Mac, but its runtime is missing. Install it with: pip install ai-edge-litertu?   wake word: no tflite runtime available — falling back to onnxr&   z'openwakeword model download skipped: %s)wakeword_modelsrD   )#toolsr  ensurer   openwakeword.modelr  rE   rF   rG   r(   r5   r.   rL   ry   
_thresholdr~   _confirm_needed_confirm_streakr[   r`   rI   rX   r?   RuntimeErrorrJ   r/   _BUNDLED_MODEL_ALIASESr7   utilsdownload_models_modellistmodelskeys_labels)
r   rB   r  r   r  rK   	model_refr'   r   r  s
             r   __init__z_OpenWakeWordEngine.__init__  s   ######,U;;;,,,,,,)3CGGN4K4KT)R)RZcggn%%%XZ((?,?@@FFHH	/44	
 's++3C88    )>)@)@ P  !;E JJJJ P P PKQOOOOOOOOP(** #"$$ &Z   `aaa"	 ?? 666.y99I	G..	{;;;; 	G 	G 	GLLBAFFFFFFFF	GeF	RRRDK.335566s0   5D 
D<D77D<1G 
G<G77G<r)   r8   c                      j                             |          }t           fd|                                D                       }|r+ xj        dz  c_         j         j        k    r	d _        dS dS d _        dS )Nc              3  .   K   | ]}|j         k    V  d S rd   )r  ).0scorer   s     r   	<genexpr>z._OpenWakeWordEngine.process.<locals>.<genexpr>T  s*      II5DO+IIIIIIr   r{   r   TF)r  predictanyr   r  r  )r   r   scoresovers   `   r   r   z_OpenWakeWordEngine.processR  s    $$U++IIIIIIIII  	  A%  #t';;;'($t5 ur   r   c                j    d| _         	 | j                                         d S # t          $ r Y d S w xY wr   )r  r  r   r`   r   s    r   r   z_OpenWakeWordEngine.reset`  sM      !	K 	 	 	DD	s   $ 
22c                .    |                                   d S rd   )r   r   s    r   r   z_OpenWakeWordEngine.closei      

r   NrB   r!   r   r   	r   r   r   r   r   r  r   r   r   r   r   r   r  r    sr        >> L97 97 97 97v           r   r  zhttps://github.com/k2-fsa/sherpa-onnx/releases/download/kws-models/sherpa-onnx-kws-zipformer-gigaspeech-3.3M-2024-01-01.tar.bz2z4sherpa-onnx-kws-zipformer-gigaspeech-3.3M-2024-01-01r   c                 .    ddl m}   |             dz  dz  S )Nr   get_hermes_homecacher,   )hermes_constantsr-  r,  s    r   _sherpa_model_rootr0  x  s,    000000?w&44r   rootOptional[Path]c                @   | pt                      } | t          z  }|dz                                  r|S ddl}ddl}|                     dd           | t           dz  }t                              d           |j        	                    t          |           |                    |d          5 }|                    | d	
           ddd           n# 1 swxY w Y   |                    d           |dz                                  st          d|           |S )zBDownload + unpack the sherpa KWS model once; return its directory.
tokens.txtr   NTparentsexist_okz.tar.bz2z:wake word: downloading sherpa KWS model (one-time, ~13 MB)zr:bz2data)filter)
missing_okz sherpa KWS model unpack failed: )r0  _SHERPA_KWS_MODEL_DIRr   tarfileurllib.requestmkdirrI   r   requesturlretrieve_SHERPA_KWS_MODEL_URLopen
extractallunlinkr  )r1  targetr<  urllibarchivetfs         r   _ensure_sherpa_modelrI  ~  sm   '%''D))F%%'' NNNJJtdJ+++-7777G
KKLMMM
N4g>>>	gw	'	' +2
d6***+ + + + + + + + + + + + + + +NNdN###\!))++ HFfFFGGGMs   0CCCc                  6    e Zd ZdZdZddZddZdd
ZddZdS )_SherpaKwsEngineuC  sherpa-onnx open-vocabulary keyword spotting — any typed phrase, zero training.

    The configured ``wake_word.phrase`` is BPE-tokenized at runtime against the
    model's vocabulary, so "hey hermes", "hey coder", or any other phrase works
    immediately. Here ``phrase`` is DETECTION config, not a cosmetic label.
    r   rB   r!   c           	     n   ddl m} |                    dd           dd l}ddlm} t          |                    d          t                    r|                    d          ni }t          |                    d          pd	          	                                }|rt          |          nt                      d
z                                  st          d           t          t          |d          pd          	                                }t                      }||i}	t!          |                    dd                    rNt#                                                      D ]-\  }
}|	                    |	                                |
           .t)          |	          } |d |D             t          d
z            dt          dz                      }dd l}i | _        |                    ddddd          }t1          ||          D ]l\  }}|                                                    dd          }|	|         | j        |<   |                    d                    |          d| dz              m|                                 |j        | _        d | _         ddtC          |          z  z   }d(fd"}|"                    t          d
z             |d#           |d$           |d%          | j        |d&'          | _#        | j#        $                                | _%        d S ))Nr   r  wake.sherpaFr  )
text2tokensherpa	model_dirr$   r4  zsherpa KWS model not found at r   r   profile_routingTc                6    g | ]}|                                 S r   )upper)r  ps     r   
<listcomp>z-_SherpaKwsEngine.__init__.<locals>.<listcomp>  s     (((1QWWYY(((r   bpez	bpe.model)tokenstokens_type	bpe_modelwz.txtzhermes-kws-zutf-8)modesuffixprefixdeleteencoding r   z @
g?g?patternr(   r)   c                    t                              |                     }|st          d d|            t          |d                   S )Nzsherpa KWS model file missing: /r   )sortedglobr  r(   )rb  hitsds     r   _model_filez._SherpaKwsEngine.__init__.<locals>._model_file  sR    !&&//**D T"#RQ#R#R#R#RSSStAw<<r   zencoder-*[!8].onnxzdecoder-*[!8].onnxzjoiner-*[!8].onnxr{   )rW  encoderdecoderjoinerkeywords_filekeywords_thresholdnum_threads)rb  r(   r)   r(   )&r  r  r  sherpa_onnxrN  rE   rF   rG   r(   r.   r   rI  r   r  rf   r   r8   r   itemsrW   r  tempfile_display_to_profileNamedTemporaryFileziprS  replacewriter2   r   r   _keywords_filer   ry   KeywordSpotter_spottercreate_stream_stream)r   rB   r  rp  rN  rK   rP  r   own_profile
phrase_mapprofrT  r   rW  rr  kwtoksdisplay	thresholdri  rh  s                       @r   r  z_SherpaKwsEngine.__init__  sy   ######u555******#-cggh.?.?#F#FNcgghB,,23399;;	(DDOOO.B.D.DL ((** 	ECCCDDD T#x((8L99??AA*,,&,k%:
)40011 	7355;;== 7 7a%%aggii6666z""(((((q<'((!k/**	
 
 
 	 46 ((VM%RY ) 
 
 7F++ 	8 	8GAtggii''S11G0:1D$W-HHSXXd^^&67&6&6&667777



 g59 3c!2!222		  	  	  	  	  	  $22q<'((K 455K 455;233-( 3 
 
 }2244r   r)   r8   c                |   dd l }|                    ||j                  dz  }| j                            t
          |           d}| j                            | j                  r| j                            | j                   | j        	                    | j                  }|rxd}t          |          }|                    dd                                          | j                            |d          f| _        | j                            | j                   | j                            | j                  |S )	Nr   r   g      @FTr   r`  r$   )r   r   float32r|  accept_waveformr   rz  is_readydecode_stream
get_resultr(   rv  r/   rs  rF   r   reset_stream)r   r   r   samplesfiredresultr  s          r   r   z_SherpaKwsEngine.process  s!   **U"**55?$$[':::m$$T\22 	9M''555]--dl;;F 9f++OOC--3355,00"==#
 **4<888 m$$T\22 	9 r   r   c                f    	 | j                                         | _        d S # t          $ r Y d S w xY wrd   )rz  r{  r|  r`   r   s    r   r   z_SherpaKwsEngine.reset  sB    	=6688DLLL 	 	 	DD	s   " 
00c                \    	 t          j        | j                   d S # t          $ r Y d S w xY wrd   )r0   rD  rx  r   r   s    r   r   z_SherpaKwsEngine.close  sB    	Id)***** 	 	 	DD	    
++Nr)  r   r   r*  r   r   r   rK  rK    sz          LG5 G5 G5 G5R   (        r   rK  c                  *    e Zd ZdZddZddZdd	Zd
S )_PorcupineEngineu@   Picovoice Porcupine — premium, on-device, needs an access key.rB   r!   c                R   ddl m} |                    dd           dd l}t	          j        d          pd                                }|st          d          t          |	                    d	          t                    r|	                    d	          ni }t          |	                    d
          pd                                          }dt          |          z
  }||gd}t          |          r|g|d<   n|g|d<    |j        di || _        | j        j        | _        d S )Nr   r  wake.porcupineFr  PORCUPINE_ACCESS_KEYr$   zcPorcupine wake word requires PORCUPINE_ACCESS_KEY (get a free key at https://console.picovoice.ai).	porcupinekeywordjarvisrs   )
access_keysensitivitieskeyword_pathskeywordsr   )r  r  r  pvporcupiner0   getenvr.   r  rE   rF   rG   r(   ry   r   create
_porcupiner   )	r   rB   r  r  r  rK   r  porcupine_sensitivitykwargss	            r   r  z_PorcupineEngine.__init__  sW   ######)%888i 677=2DDFF
 	D  
 '11E1Et&L&LTcggk"""RTcggi((4H55;;==
 !$l3&7&7 70:NcMd!e!eG$$ 	+'.iF?##")F:,+,66v66 O8r   r)   r8   c                >    | j                             |          dk    S r   )r  r   r   s     r   r   z_PorcupineEngine.process-  s    &&u--22r   r   c                \    	 | j                                          d S # t          $ r Y d S w xY wrd   )r  r^  r`   r   s    r   r   z_PorcupineEngine.close1  sC    	O""$$$$$ 	 	 	DD	r  Nr)  r   r   )r   r   r   r   r  r   r   r   r   r   r  r    sV        JJ9 9 9 9>3 3 3 3     r   r  c                    t          |           }|dk    rt          |           S |dv rt          |           S |dv rt          |           S t	          d|          )Nr  rO  sherpa-onnxkwsrB  )r   owwr   zUnknown wake_word provider: )ri   r  rK  r  ru   )rB   r   s     r   _build_enginer  8  sq    ~~H;$$$;;;$$$333"3'''
@H@@
A
AAr   c                     	 ddl m} m}m}  |            } ||          o | |          dk    S # t          $ r Y dS w xY w)u  Is a speech-to-text provider configured and enabled?

    A wake without STT arms the mic but every captured utterance dies at
    transcription — a useless (and confusing) experience. Same standard as
    voice mode's ``check_voice_requirements``: enabled + a real provider.
    r   )_get_provider_load_stt_configis_stt_enablednoneF)tools.transcription_toolsr  r  r  r`   )r  r  r  
stt_configs       r   
_stt_readyr  G  sy    ]]]]]]]]]]%%''
~j))QmmJ.G.G6.QQ   uus   -0 
>>c                 z   	 ddl m} m}  |  |                      }n# t          $ r Y dS w xY wdddd}|                    |          }|B	 dd	lm} |                    |          s|                                S n# t          $ r Y dS w xY w	 dd
l m	} t           |                      S # t          $ r Y dS w xY w)u  Can the configured text-to-speech provider run (or install at first use)?

    The wake flow is fully hands-free (wake → speak → hear the reply); without
    TTS the reply is silent and the loop is pointless.

    PROBE, not an installer: ``check_tts_requirements`` lazily pip-installs the
    provider SDK via ``_import_*`` → ``lazy_deps.ensure`` — running that inside
    a status poll froze wake.status for the length of a pip install (and a
    failed install marked the wake word unavailable, unmounting the desktop
    ear). When the provider's deps aren't installed yet, "installable at first
    use" counts as ready and we never touch pip from here.
    r   )r  _load_tts_configFztts.edgeztts.elevenlabsztts.mistral)edge
elevenlabsmistralNr  )check_tts_requirements)tools.tts_toolr  r  r`   rF   r  r  is_available_allow_lazy_installsr  r8   )r  r  r   _LAZY_TTS_FEATURESfeaturer  r  s          r   
_tts_readyr  W  sH   BBBBBBBB =!1!1!3!344   uu &  
 !$$X..G	''''''))'22 8 557778  	 	 	55	999999**,,---   uus0    
,,.A= =
B
BB, ,
B:9B:c                   | | nt                      } t          |           }ddlm} |dk    rd}n	|dv rd}nd}|                    |          }|                                }|rt                      nd	}d
}t                      }t                      }	d}
d
}|dvr:t          |           }|dk    r%t                      p|                    d          p|}|dk    r-t          j        d          pd                                sd	}d}
nj|s|s|                    |          pd}
nN|sd}
nI|r|st          |           dk    rd}
n/|r|	s+d                    d d|fd|	ffD                       }d| d}
t          |           }|rt#                      nd	}|dk    r#|p| o|}|r|
s|
                    d          rd}
n|r|p| o|}|r|s|
sd}
|o|o|	o|o|||||||||	t'          |           |
dS )zDReport whether wake-word detection can run, with a remediation hint.Nr   r  r  r  r  rM  r  FTr$   )r  rO  r  r  rB  r+   r
  r  zDSet PORCUPINE_ACCESS_KEY (free key at https://console.picovoice.ai).zNThe wake word needs the tflite runtime on this Mac: pip install ai-edge-litertr   zHMicrophone capture needs sounddevice + numpy and a working audio device.z and c              3  $   K   | ]\  }}||V  d S rd   r   )r  r   oks      r   r!  z/check_wake_word_requirements.<locals>.<genexpr>  s@       
 
T2ac

 
 
 
 
 
r   zspeech-to-textztext-to-speechzWake word needs uN    configured — run `hermes tools` (Voice section) or see the voice-mode docs.r   zMicrophone capture needsu   No local microphone on this backend. Remote desktop can stream the client mic — set wake_word.capture: client or use a desktop build with client-capture wake support.)	availabler   deps_availableaudio_availablelocal_input_availabler   access_key_setstt_availabletts_availabler   hint)ra   ri   r  r  r  r  r   r  r  rL   r[   r0   r  r.   feature_install_commandr   r2   r   
startswithr   )rB   r   r  r  deps_oklazy_okaudio_okkey_okstt_oktts_okr  	tflite_okr'   missingcapture_modelocal_input_okmic_oks                    r   check_wake_word_requirementsr    s   ##&;&=&=C~~H;"	=	=	=%$$W--G,,..G &-7!!!%HF \\F\\FD ILLL/44	  -//p93I3IJd3e3epipI;	2H(I(I(OR'V'V'X'XU @W @0099?R 	@_	 @ @&:3&?&?7&J&JY @6 @,, 
 
#3V"<?OQW>X!Y
 
 
 
 
?7 ? ? ? (,,L4;F.000Nx5[4W 	4 	9:: &hDK,CG 	8 	D 	:  JJ6JiJF!#!/ c""  r   c                  |    e Zd ZdZedddfd dZed!d            Zd"dZd"dZ	d"dZ
d"dZd"dZd"dZd"dZd#dZdS )$WakeWordDetectorzBackground hotword listener. Fires ``on_wake()`` when the phrase is heard.

    The engine is built once and kept alive across pause/resume; only the audio
    stream + reader thread cycle, so toggling the mic for a voice turn is cheap.
    NFenginer   on_wakeCallable[[], None]cooldownrp   
on_failure.Optional[Callable[['WakeWordDetector'], None]]r   rj   external_audior8   c                   || _         || _        || _        || _        || _        t          |          | _        | j        rddddnd|i| _        d | _        t          j
                    | _        t          j
                    | _        d| _        t          j                    | _        dd l}|                    d	          | _        d
| _        d| _        d S )Nr   zclient capturer   )r   r   r   r   rr   r   @   )maxsizeF)r  r  r  r  r   r8   r  input_device_details_thread	threadingEvent_stop_callback_inflight
_last_fireLock_lockqueueQueue_audio_qaudio_silent_silent_frames)r   r  r  r  r  r   r  _queues           r   r  zWakeWordDetector.__init__  s    
  $(">22 ",+;QQQl+ 	!
 48_&&
"+/"3"3^%%
-3\\"\-E-E "r   r)   c                @    | j         }|d uo|                                S rd   )r  is_aliver   ts     r   runningzWakeWordDetector.running  s     L}--r   r   c                d   | j         sdS 	 ddl}n# t          $ r Y dS w xY wt          |t          t
          t          f          r|                    ||j                  }n/|	                    ||j                  
                    d          }t          | j        j                  }|dk    rdS d}t          |j        d                   }||k     r||||z            }||z  }|j        d         |k     r0|                    ||j                  }||d|j        d         <   |}	 | j                            |           ng# t          $ rZ 	 | j                                         n# t          $ r Y nw xY w	 | j                            |           n# t          $ r Y nw xY wY nw xY w||k     dS dS )zEnqueue one int16 mono frame (or raw bytes) for client capture.

        Frame length should match ``engine.frame_length`` (typically 1280 samples
        at 16 kHz). Short frames are zero-padded; long frames are split.
        Nr   r   r   )r  r   r`   rE   bytes	bytearray
memoryview
frombufferr   r   r   rl   r  r   shaper   r  
put_nowait
get_nowait)	r   	pcm_int16r   arrfloffsetr}   chunkpads	            r   feedzWakeWordDetector.feed	  s
    " 	F	 	 	 	FF	i%J!?@@ 	D--	-::CC**Ybh*77??CCC)**77F	!qjj",-EbLF{1~""hhrh22(-$ek!n$%((//// 	 	 	M,,....    DM,,U3333    D	 qjjjjjjsf    
&E 
F%E&%F%&
E30F%2E33F%7FF%
FF%FF%$F%c                H   | j         5  | j        '| j                                        r	 ddd           dS | j                                         t          j                    }g }t          j        | j        ||fdd          | _        | j        	                                 ddd           n# 1 swxY w Y   |
                    t                    s#|                                  t          d          |r*|                                  t          d          |d         dS )z@Open the mic (or client feeder) and begin listening. Idempotent.NTz	wake-word)rE  argsdaemonr   z1Timed out while opening the wake-word microphone.z(Failed to open the wake-word microphone.r   )r  r  r  r  clearr  r  Thread_runstartwait_START_TIMEOUT_SECONDS_halt_threadTimeoutErrorr  )r   readystartup_errorss      r   r  zWakeWordDetector.start3  s   Z 	! 	!|'DL,A,A,C,C'	! 	! 	! 	! 	! 	! 	! 	! JO%%E24N$+y^, 	  DL L   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! zz011 	TRSSS 	bIJJP^_`Paa	b 	bs   "B.A+B..B25B2c                .    |                                   d S rd   )r  r   s    r   pausezWakeWordDetector.pauseJ  s    r   c                .    |                                   d S rd   )r  r   s    r   resumezWakeWordDetector.resumeM  r(  r   c                `    |                                   | j                                         d S rd   )r  r  r   r   s    r   stopzWakeWordDetector.stopP  s.    r   c                    | j         5  | j                                         | j        }|+|t	          j                    ur|                    d           | j        |u rd | _        d d d            d S # 1 swxY w Y   d S )Nr	   timeout)r  r  setr  r  current_threadr2   r  s     r   r  zWakeWordDetector._halt_threadT  s    Z 	$ 	$JNNA})*B*D*D!D!Ds###|q  #	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$ 	$s   AA33A7:A7c                   	 |                                   n2# t          $ r%}t                              d|           Y d }~nd }~ww xY w| j                                         d S # | j                                         w xY w)Nzwake word callback failed: %s)r  r`   rI   rJ   r  r
  )r   r   s     r   _dispatch_wakezWakeWordDetector._dispatch_wake]  s    	,LLNNNN 	? 	? 	?NN:A>>>>>>>>	? #))+++++D#))++++s*    A$ 
AAA$ AA$ $A?r  threading.Eventr  list[BaseException]c           
     R   | j         j        }|}t          }d }d }| j        rO	 	 | j                                         # t          $ r Y nw xY wt                              d|t                     n	 t                      \  }}nc# t          t          f$ rO}	t                              d|	           |                    |	           |                                 Y d }	~	d S d }	~	ww xY wt          || j                  | _        t%          | j                  }t'          dt)          t+          ||z  t          z                                }t                              d| j                            d          pd| j        | j                            d          pd	| j                            d
          pd	|t                     	 |                    | j        |dd|          }|                                 n\# t          $ rO}	t                              d|	           |                    |	           |                                 Y d }	~	d S d }	~	ww xY w	 | j                                          n# t          $ r Y nw xY wt                              d|t          | j                   |                                 d}
t'          dt)          t4          t          z  t'          d|          z                      }	 | j                                        s$	 | j        rR	 | j                            d          }n2# t          $ r% | xj        dz  c_        | j        |k    rd| _        Y mw xY w|}n|                    |          \  }}nM# t          $ r@}	t                               d|	           | j                                         }
Y d }	~	nld }	~	ww xY wtC          |dd          dk    r|d d df         n|}|t          k    rtE          |||          }	 tG          |          r.t)          tI          |                                                    nd}n# t          $ r tJ          dz   }Y nw xY w|tJ          k    r\| xj        dz  c_        | j        |k    r@d| _        t                               dtJ          t4          tM          | j                             n6| j        r/| j        rt                              d           d| _        d| _        	 | j         '                    |          }n3# t          $ r&}	t          (                    d|	           Y d }	~	Xd }	~	ww xY w|rtS          j*                    }|| j+        z
  | j,        k    r|| _+        t                              d           | j-                                        sG| j-                                         t]          j/        | j0        dd                                           nt          (                    d           | j                                        $|:	 |1                                 |2                                 n# t          $ r Y nw xY wt                              d           |
r| j3        | 3                    |            d S d S d S # |:	 |1                                 |2                                 n# t          $ r Y nw xY wt                              d           |
r| j3        | 3                    |            w w w xY w)NTuL   wake word: client-capture mode (frame=%d, rate=%d) — waiting for wake.feedz*wake word: audio libraries unavailable: %sr{   zmwake word: opening microphone device=%s selector=%r hostapi=%s default_rate=%s capture_rate=%d engine_rate=%dr   r   r   unknownr   r   )device
samplerater   r   	blocksizez(wake word: failed to open microphone: %sz5wake word: listening (frame=%d, rate=%d, external=%s)Fg      ?r  z wake word: stream read error: %sndim   r   z;wake word: mic delivers only silence (peak<=%d for %ds); %su0   wake word: mic audio detected — stream healthyzwake word: engine error: %su.   wake word: phrase detected — firing callbackzwake-word-callback)rE  r	  r   u0   wake word: detection within cooldown — ignoredzwake word: stream closed)4r  r   r   r  r  r  r`   rI   r   r   rQ   r   r   appendr  r   r   r  r   rw   rl   r   rF   InputStreamr  r   _SILENCE_ALERT_SECONDSr  is_setr  r  readrJ   r   r   lenabs_SILENCE_PEAKr   r   rX   time	monotonicr  r  r  r  r  r   r  r   r  )r   r  r  r   capture_frame_lengthcapture_rater   streamr   r   failedsilent_alert_framesr   r8  	_overflowpeakr  nows                     r   r  zWakeWordDetector._rune  s   {/+" 0	/M,,.../   KK^k   
&BB)   I1MMM%%a(((			 )?r4CT(U(UD%/0IJJL#&3u\L8;FGGHH$ $  KKA)--f55I9I!)--i88EI)--.BCCPy	 	 	,+!2 (      GKKK%%a(((				K 	 	 	D	 	K +t/B	D 	D 	D		 "!S)?+)MPSTUWcPdPd)d%e%effD	&j'')) 9Y* L%$(M$5$5d$5$C$CEE( % % % //14//#26III48 1$H%  %*0++6J*K*Ki    NN#EqIII!%!2!2!4!44FEEEE '.dFA&>&>!&C&CQQQT

;..1"e\JJE-47JJE3s5zz~~//000ADD  - - -(1,DDD-=((''1,''*.AAA,0)Y)+A-d.GHH  
 ( .( X$VWWW*+D'(-D% K//66EE    LL!>BBBHHHH  Y.**CT_,==*-$TUUU#6==?? & 377999%,'+':'+%9   $eggg%WXXXs j'')) 9Yv !KKMMMLLNNNN    DKK2333 &$/5%%%%%& &55 !KKMMMLLNNNN    DKK2333 &$/5%%%%&5s:  = 
A
	A
1B C#ACC#3G6 6
I AI

II- -
I:9I:6Z. N L5 4N 5,M$!N "Z. #M$$N Z. 
O5OZ. OAZ. ?Q Z. Q'$Z. &Q''B Z. T# "Z. #
U-UZ. UC!Z. 7(Y   
Y-,Y-.\&2([\&
[(%\&'[((>\&)r  r   r  r  r  rp   r  r  r   rj   r  r8   r   r   )r  r!  r  r"  r)   r   )r   r   r   r   _FIRE_COOLDOWN_SECONDSr  propertyr  r  r  r  r  r  r  r   r  r   r   r   r  r    s         $:NR26(-	         > . . . X.( ( ( (Tb b b b.         $ $ $ $, , , ,M& M& M& M& M& M&r   r  zOptional[WakeWordDetector]	_detectorzobject | None_detector_ownerc                 .    ddl m}   |             dz  dz  S )Nr   get_default_hermes_rootruntimezwake-word.lock)r/  rB  rA  s    r   
_lock_pathrD    s.    888888""$$y03CCCr   r1   c                   | pt                      }|j                            dd           t          |d          }	 t          j        dk    rddl}|                    dt          j                   |	                                dk    r)|
                    d           |                                 |                    d           |                    |                                |j        d           n9ddl}|                    |                                |j        |j        z             n=# t&          t(          f$ r)}|                                 t-          d	          |d}~ww xY w|S )
zCAcquire the cross-process microphone lease, or raise WakeWordInUse.Tr5  za+bntr   N    r{   &Wake-word microphone is already owned.)rD  parentr>  rB  r0   r   msvcrtseekSEEK_ENDtellrw  flushlockingfilenoLK_NBLCKfcntlflockLOCK_EXLOCK_NBr   BlockingIOErrorr   r   )r1   	lock_pathhandlerJ  rR  r   s         r   _acquire_machine_lockrY    sK   $
I4$777)U##FM7d??MMMKK2;'''{{}}!!U###KKNNNNN6==??FOQ????LLLKK)FGGG_% M M MDEE1LM Ms   C2D1 1E+$E&&E+r   c                   | d S 	 t           j        dk    rHdd l}|                     d           |                    |                                 |j        d           n1dd l}|                    |                                 |j	                   n# t          $ r Y nw xY w|                                  d S # |                                  w xY w)NrF  r   r{   )r0   r   rJ  rK  rO  rP  LK_UNLCKrR  rS  LOCK_UNr   r   )rX  rJ  rR  s      r   _release_machine_lockr]    s    ~7d??MMMKKNNNNN6==??FOQ????LLLKK777    	s*   B	B B6 
BB6 BB6 6Cdetectorc                   t           5  t          | ur	 ddd           dS t          }dadada	 | j                                         t          |           n# t          |           w xY w	 ddd           dS # 1 swxY w Y   dS )zDRelease ownership if the active microphone stream dies unexpectedly.N)_detector_lockr>  _detector_file_lockr?  r  r   r]  )r^  lock_handles     r   _detector_failedrc  1  s     
 
/ 
/H$$
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ *	"	/O!!###!+....!+.....
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/ 
/s-   A9A9AA9A((A99A= A=)configr  r  r  ownerobjectrd  r  c          	        |t          d          t          5  t          Pt          |urt	          d          | t          _        t                                           t          cddd           S t                      }	 ||nt                      }t          |          }t          || t          t          |          |          }|a|a|a|                                 |cddd           S # t          $ rI t          +	 t                                           n# t          $ r Y nw xY wdadadat#          |            w xY w# 1 swxY w Y   dS )a/  Claim, build, and start the detector. Idempotent for the same owner.

    Raises if engine construction fails (missing deps / access key / model);
    callers should probe :func:`check_wake_word_requirements` first. A different
    owner, including another process, receives :class:`WakeWordInUse`.
    Nz wake-word owner must not be NonerH  )r  r   r  )ru   r`  r>  r?  r   r  r  rY  ra   r  r  rc  ro   ra  r  r`   r  r]  )r  re  rd  r  rb  rB   r  r^  s           r   start_listeningrh  A  s    };<<< 
 ! ! e++#$LMMM 'I! ! ! ! ! ! ! ! ,--	".&&4I4K4KC"3''F'+*3//-  H !I#O"-NN-! ! ! ! ! ! ! !.  
	 
	 
	$NN$$$$    DI"O"&!+...
	/! ! ! ! ! ! ! ! ! !sO   AE1E A"C//EDE
D(%E'D((EEE	E	c                h    t           5  t          d uot          | u cd d d            S # 1 swxY w Y   d S rd   )r`  r>  r?  re  s    r   owns_listenerrk  v  s    	 B B$AE)AB B B B B B B B B B B B B B B B B Bs   '++c                    t           5  t          	t          | ur	 ddd           dS t                                           	 ddd           dS # 1 swxY w Y   dS )z;Release the microphone only when ``owner`` holds the lease.NFT)r`  r>  r?  r  rj  s    r   pause_listeningrm  {  s    	  u < <        		                    AAAAc                    t           5  t          	t          | ur	 ddd           dS t                                           	 ddd           dS # 1 swxY w Y   dS )z;Re-open the microphone only when ``owner`` holds the lease.NFT)r`  r>  r?  r  rj  s    r   resume_listeningrp    s    	  u < <        		                 rn  c                   t           5  t          	t          | ur	 ddd           dS t          }t          }dadada	 |                                 t          |           n# t          |           w xY w	 ddd           dS # 1 swxY w Y   dS )z<Fully stop the detector only when ``owner`` holds the lease.NFT)r`  r>  r?  ra  r  r]  )re  detrb  s      r   stop_listeningrs    s     
  u < <        )	"	/HHJJJ!+....!+....                 s-   BBA B A11BB	Bc                 d    t           5  t          } d d d            n# 1 swxY w Y   | d uo| j        S rd   )r`  r>  r  rr  s    r   is_listeningrv    ss    	                d?*s{*     c                 d    t           5  t          } ddd           n# 1 swxY w Y   | duo| j        S )a  True when the armed stream has delivered only silence (dead mic).

    The stream opens fine but every frame is zeros, so detection can never
    fire. Lets status surfaces show "listening but the microphone appears
    silent" instead of a healthy state.
    N)r`  r>  r  ru  s    r   audio_is_silentry    sv     
                d?/s//rw  c                b   t           5  t          }ddd           n# 1 swxY w Y   |t          |j                  S | | nt	                      } t          |           }	 t                      \  }}n0# t          t          f$ r}|t          |          dcY d}~S d}~ww xY wt          ||          S )zDReturn configured/active PortAudio input diagnostics for status UIs.N)r   r   )r`  r>  rG   r  ra   ro   r   rQ   r   r(   r   )rB   rr  r   r   r   r   s         r   get_input_device_statusr{    s   	                
C,---##&;&=&=CS!!H7AA! 7 7 7$s1vv666666667!"h///s*     A1 1BBBBr   c                     t           5  t          } ddd           n# 1 swxY w Y   | dS t          | j        dd          S )z(matched phrase, profile) of the most recent wake fire, if the engine
    reports per-phrase matches (sherpa multi-profile routing). None otherwise.Nr   )r`  r>  r   r  ru  s    r   get_last_matchr}    s     
                
{t3:|T222rw  c                    t           5  t          	t          | ur	 ddd           dS t          j        s	 ddd           dS t          }ddd           n# 1 swxY w Y   |                    |           dS )zPush client-captured PCM into the armed detector (client capture mode).

    Returns True when the frame was accepted for ``owner``'s armed detector.
    NFT)r`  r>  r?  r  r  )re  r   rr  s      r   
feed_audior    s    
 
  u < <        ' 		       
                HHY4s   AAAAAc                     t           5  t          } ddd           n# 1 swxY w Y   | 
t          ddS t          t          t	          | j        dd          pd          t          | j                  dS )z8Sample rate + frame length for client capture streamers.Nr   )sample_rater   r   )r  r   r  )r`  r>  r   rl   r   r  r8   r  ru  s    r   detector_frame_infor    s    	                
{*DAAA"GCJEEMNNs122  rw  )r&   )r'   r(   r)   r(   r   )r)   r(   )rB   r!   r)   r(   )r)   r!   )rB   r!   rb   r(   r)   r   )rB   r!   r)   rj   )rB   r!   r)   rp   )rB   r!   r)   rl   rd   )rB   r   r)   r(   )rB   r   r   r8   r   r8   r)   r(   )r   r(   rB   r   r)   r8   )r)   r   )r   rj   r)   r!   )r   r!   r)   r(   )r   r!   r)   rl   )r   rl   )rn   r(   r)   r8   )r)   r   )r1  r2  r)   r   )rB   r!   r)   r   )rB   r   r)   r!   )r1   r2  r   )r^  r  r)   r   )
r  r  re  rf  rd  r   r  r8   r)   r  )re  rf  r)   r8   )r)   r   )Zr   
__future__r   loggingr0   r=   r  r2  pathlibr   typingr   r   r   r   	getLoggerr   rI   r   r<  r  r|   r1  r,  r  r   r"   r   r5   	frozensetr  r7   r?   rA   rH   rL   r[   ra   rf   ri   ro   ry   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  rA  r;  r0  rI  rK  r  r  r  r  r  r  r>  r?  ra  r  r`  rD  rY  r]  rc  rh  rk  rm  rp  rs  rv  ry  r{  r}  r  r  r   r   r   <module>r     s    > # " " " " "  				 



            0 0 0 0 0 0 0 0 0 0 0 0		8	$	$      !   N N N N NL N N N 
 7 	    $ # "#M#M#MNN ` ` ` ` `F F F F5 5 5 5     F   <0 0 0 06 6 6 6
H H H H   ! ! ! !   4 4 4 4 4 %)  	     D   <= = = = =$      L     " " " "J8 8 8 8   @ @ @ @.   0       ,   Y Y Y Y Y' Y Y YBC  O 5 5 5 5    *u u u u uw u u up* * * * *w * * *ZB B B B    ) ) ) )XT T T T TvT& T& T& T& T& T& T& T&v )-	 , , , ,!% % % % % !!D D D D    2   &/ / / /( (, 2 2 2 2 2 2jB B B B
         $+ + + +	0 	0 	0 	00 0 0 0 0 3 3 3 3   
 
 
 
 
 
r   