integration.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. """
  2. Integration module for Voice Recognition with Trixy Event System and Wakeword Detection.
  3. This module provides seamless integration between the voice recognition system
  4. and the existing Trixy infrastructure, including event handling, wakeword workflow,
  5. and satellite communication.
  6. """
  7. import logging
  8. import threading
  9. import time
  10. from typing import Dict, List, Optional, Any, Callable, Union
  11. from dataclasses import dataclass, field
  12. from datetime import datetime
  13. import numpy as np
  14. import torch
  15. from .detector import VoiceRecognitionDetector, SpeakerIdentificationResult, AudioChunk
  16. from .config import VoiceRecognitionConfig
  17. from .speaker_manager import SpeakerManager, SpeakerInfo
  18. # Try to import event system components
  19. try:
  20. from trixy_core.events import (
  21. EventHandler, TrixyEvent, EventType, TrixyEventData,
  22. WakewordReceivedEventData, RawAudioInputReceivedEventData, SpeakerInfo as EventSpeakerInfo
  23. )
  24. EVENTS_AVAILABLE = True
  25. except ImportError:
  26. # Create mock classes if event system not available
  27. EVENTS_AVAILABLE = False
  28. EventHandler = object
  29. TrixyEvent = lambda events: lambda func: func
  30. EventType = object
  31. TrixyEventData = object
  32. WakewordReceivedEventData = object
  33. RawAudioInputReceivedEventData = object
  34. EventSpeakerInfo = object
  35. # Configure logging
  36. logger = logging.getLogger(__name__)
  37. @dataclass
  38. class VoiceRecognitionEventData(TrixyEventData if EVENTS_AVAILABLE else object):
  39. """Event data for voice recognition events."""
  40. event_type: str = ""
  41. speaker_id: Optional[str] = None
  42. speaker_name: Optional[str] = None
  43. confidence: float = 0.0
  44. similarity_score: float = 0.0
  45. processing_time_ms: float = 0.0
  46. audio_quality: float = 0.0
  47. vad_confidence: float = 0.0
  48. timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
  49. additional_info: Dict[str, Any] = field(default_factory=dict)
  50. @dataclass
  51. class SpeakerEnrolledEventData(TrixyEventData if EVENTS_AVAILABLE else object):
  52. """Event data for speaker enrollment."""
  53. speaker_id: str = ""
  54. speaker_name: str = ""
  55. enrollment_success: bool = False
  56. embedding_count: int = 0
  57. quality_score: float = 0.0
  58. verification_score: float = 0.0
  59. message: str = ""
  60. timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
  61. @dataclass
  62. class SpeakerRemovedEventData(TrixyEventData if EVENTS_AVAILABLE else object):
  63. """Event data for speaker removal."""
  64. speaker_id: str = ""
  65. speaker_name: str = ""
  66. timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
  67. class VoiceRecognitionIntegration:
  68. """
  69. Integration layer between Voice Recognition system and Trixy infrastructure.
  70. This class handles:
  71. - Event system integration
  72. - Wakeword detection enhancement
  73. - Satellite communication coordination
  74. - Performance monitoring and reporting
  75. """
  76. def __init__(self,
  77. voice_detector: VoiceRecognitionDetector,
  78. event_handler: Optional[EventHandler] = None,
  79. enable_wakeword_enhancement: bool = True,
  80. enable_continuous_recognition: bool = False):
  81. """
  82. Initialize voice recognition integration.
  83. Args:
  84. voice_detector: Voice recognition detector instance
  85. event_handler: Event handler for system integration
  86. enable_wakeword_enhancement: Whether to enhance wakeword events with speaker info
  87. enable_continuous_recognition: Whether to run continuous recognition
  88. """
  89. self.voice_detector = voice_detector
  90. self.event_handler = event_handler
  91. self.enable_wakeword_enhancement = enable_wakeword_enhancement
  92. self.enable_continuous_recognition = enable_continuous_recognition
  93. # Integration state
  94. self._is_running = False
  95. self._continuous_thread: Optional[threading.Thread] = None
  96. self._stop_event = threading.Event()
  97. # Performance tracking
  98. self._performance_stats = {
  99. 'total_recognitions': 0,
  100. 'successful_recognitions': 0,
  101. 'wakeword_enhancements': 0,
  102. 'average_processing_time_ms': 0.0,
  103. 'last_recognition': None
  104. }
  105. # Event callbacks
  106. self.on_speaker_identified: Optional[Callable] = None
  107. self.on_speaker_verified: Optional[Callable] = None
  108. self.on_recognition_failed: Optional[Callable] = None
  109. logger.info("Voice Recognition Integration initialized")
  110. # Register event handlers if event system is available
  111. if EVENTS_AVAILABLE and self.event_handler:
  112. self._register_event_handlers()
  113. def start(self):
  114. """Start the voice recognition integration."""
  115. if self._is_running:
  116. logger.warning("Voice recognition integration already running")
  117. return
  118. self._is_running = True
  119. self._stop_event.clear()
  120. # Start voice detector
  121. self.voice_detector.start()
  122. # Start continuous recognition if enabled
  123. if self.enable_continuous_recognition:
  124. self._continuous_thread = threading.Thread(
  125. target=self._continuous_recognition_loop,
  126. daemon=True
  127. )
  128. self._continuous_thread.start()
  129. # Trigger start event
  130. if EVENTS_AVAILABLE and self.event_handler:
  131. try:
  132. event_data = VoiceRecognitionEventData(
  133. event_type="voice_recognition_started",
  134. speaker_id=None,
  135. speaker_name=None,
  136. confidence=0.0,
  137. similarity_score=0.0,
  138. processing_time_ms=0.0,
  139. audio_quality=0.0,
  140. vad_confidence=0.0,
  141. additional_info={'continuous_mode': self.enable_continuous_recognition}
  142. )
  143. self.event_handler.trigger_event("voice_recognition_started", event_data)
  144. except Exception as e:
  145. logger.error(f"Error triggering start event: {e}")
  146. logger.info("Voice Recognition Integration started")
  147. def stop(self):
  148. """Stop the voice recognition integration."""
  149. if not self._is_running:
  150. return
  151. self._is_running = False
  152. self._stop_event.set()
  153. # Stop continuous recognition thread
  154. if self._continuous_thread and self._continuous_thread.is_alive():
  155. self._continuous_thread.join(timeout=5.0)
  156. # Stop voice detector
  157. self.voice_detector.stop()
  158. # Trigger stop event
  159. if EVENTS_AVAILABLE and self.event_handler:
  160. try:
  161. event_data = VoiceRecognitionEventData(
  162. event_type="voice_recognition_stopped",
  163. speaker_id=None,
  164. speaker_name=None,
  165. confidence=0.0,
  166. similarity_score=0.0,
  167. processing_time_ms=0.0,
  168. audio_quality=0.0,
  169. vad_confidence=0.0
  170. )
  171. self.event_handler.trigger_event("voice_recognition_stopped", event_data)
  172. except Exception as e:
  173. logger.error(f"Error triggering stop event: {e}")
  174. logger.info("Voice Recognition Integration stopped")
  175. def is_running(self) -> bool:
  176. """Check if integration is running."""
  177. return self._is_running
  178. def process_wakeword_detection(self,
  179. audio_data: np.ndarray,
  180. wakeword_id: str,
  181. original_speaker_info: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
  182. """
  183. Process wakeword detection and enhance with speaker recognition.
  184. Args:
  185. audio_data: Audio data from wakeword detection
  186. wakeword_id: ID of detected wakeword
  187. original_speaker_info: Original speaker info from wakeword detector
  188. Returns:
  189. Enhanced speaker information
  190. """
  191. if not self.enable_wakeword_enhancement:
  192. return original_speaker_info or {}
  193. try:
  194. # Perform speaker identification on wakeword audio
  195. result = self.voice_detector.identify_speaker(audio_data)
  196. # Update performance stats
  197. self._update_performance_stats(result)
  198. # Create enhanced speaker info
  199. enhanced_info = {
  200. 'speaker_id': result.speaker_id,
  201. 'speaker_name': result.speaker_name,
  202. 'confidence': result.confidence,
  203. 'similarity_score': result.confidence, # For backward compatibility
  204. 'vr_processing_time_ms': result.processing_time_ms,
  205. 'vr_audio_quality': result.audio_quality,
  206. 'vr_vad_confidence': result.vad_result.confidence if result.vad_result else 0.0,
  207. 'vr_timestamp': result.timestamp,
  208. 'wakeword_id': wakeword_id
  209. }
  210. # Merge with original info if available
  211. if original_speaker_info:
  212. enhanced_info.update(original_speaker_info)
  213. # Trigger enhanced wakeword event
  214. if EVENTS_AVAILABLE and self.event_handler:
  215. try:
  216. event_data = VoiceRecognitionEventData(
  217. event_type="wakeword_enhanced",
  218. speaker_id=result.speaker_id,
  219. speaker_name=result.speaker_name,
  220. confidence=result.confidence,
  221. similarity_score=result.confidence,
  222. processing_time_ms=result.processing_time_ms,
  223. audio_quality=result.audio_quality,
  224. vad_confidence=result.vad_result.confidence if result.vad_result else 0.0,
  225. additional_info={'wakeword_id': wakeword_id}
  226. )
  227. self.event_handler.trigger_event("wakeword_enhanced", event_data)
  228. self._performance_stats['wakeword_enhancements'] += 1
  229. except Exception as e:
  230. logger.error(f"Error triggering wakeword enhanced event: {e}")
  231. # Call callback if set
  232. if self.on_speaker_identified:
  233. try:
  234. self.on_speaker_identified(result)
  235. except Exception as e:
  236. logger.error(f"Error in speaker identified callback: {e}")
  237. logger.debug(f"Enhanced wakeword with speaker: {result.speaker_name} (confidence: {result.confidence:.3f})")
  238. return enhanced_info
  239. except Exception as e:
  240. logger.error(f"Error enhancing wakeword detection: {e}")
  241. # Call failure callback if set
  242. if self.on_recognition_failed:
  243. try:
  244. self.on_recognition_failed(str(e))
  245. except Exception as e:
  246. logger.error(f"Error in recognition failed callback: {e}")
  247. return original_speaker_info or {}
  248. def process_audio_stream(self, audio_data: np.ndarray) -> Optional[SpeakerIdentificationResult]:
  249. """
  250. Process streaming audio for continuous recognition.
  251. Args:
  252. audio_data: Streaming audio data
  253. Returns:
  254. Speaker identification result if triggered
  255. """
  256. if not self._is_running:
  257. return None
  258. try:
  259. result = self.voice_detector.process_audio_stream(audio_data)
  260. if result and result.speaker_id:
  261. # Update performance stats
  262. self._update_performance_stats(result)
  263. # Trigger continuous recognition event
  264. if EVENTS_AVAILABLE and self.event_handler:
  265. try:
  266. event_data = VoiceRecognitionEventData(
  267. event_type="speaker_identified_continuous",
  268. speaker_id=result.speaker_id,
  269. speaker_name=result.speaker_name,
  270. confidence=result.confidence,
  271. similarity_score=result.confidence,
  272. processing_time_ms=result.processing_time_ms,
  273. audio_quality=0.0, # Not available in streaming mode
  274. vad_confidence=result.vad_result.confidence if result.vad_result else 0.0
  275. )
  276. self.event_handler.trigger_event("speaker_identified_continuous", event_data)
  277. except Exception as e:
  278. logger.error(f"Error triggering continuous recognition event: {e}")
  279. # Call callback if set
  280. if self.on_speaker_identified:
  281. try:
  282. self.on_speaker_identified(result)
  283. except Exception as e:
  284. logger.error(f"Error in speaker identified callback: {e}")
  285. return result
  286. except Exception as e:
  287. logger.error(f"Error in continuous recognition: {e}")
  288. return None
  289. def enroll_speaker_from_audio(self,
  290. speaker_name: str,
  291. audio_samples: List[np.ndarray],
  292. metadata: Optional[Dict[str, Any]] = None) -> bool:
  293. """
  294. Enroll a new speaker using audio samples.
  295. Args:
  296. speaker_name: Name of the speaker
  297. audio_samples: List of audio samples for enrollment
  298. metadata: Optional speaker metadata
  299. Returns:
  300. True if enrollment successful
  301. """
  302. try:
  303. success = self.voice_detector.add_speaker(speaker_name, audio_samples)
  304. if success:
  305. # Get speaker info
  306. speakers = self.voice_detector.get_speaker_list()
  307. enrolled_speaker = next((s for s in speakers if s.speaker_name == speaker_name), None)
  308. # Trigger enrollment event
  309. if EVENTS_AVAILABLE and self.event_handler and enrolled_speaker:
  310. try:
  311. event_data = SpeakerEnrolledEventData(
  312. speaker_id=enrolled_speaker.speaker_id,
  313. speaker_name=speaker_name,
  314. enrollment_success=True,
  315. embedding_count=enrolled_speaker.embedding_count,
  316. quality_score=enrolled_speaker.average_quality,
  317. verification_score=0.0, # Not available here
  318. message="Speaker enrolled successfully"
  319. )
  320. self.event_handler.trigger_event("speaker_enrolled", event_data)
  321. except Exception as e:
  322. logger.error(f"Error triggering speaker enrolled event: {e}")
  323. logger.info(f"Successfully enrolled speaker: {speaker_name}")
  324. else:
  325. logger.warning(f"Failed to enroll speaker: {speaker_name}")
  326. return success
  327. except Exception as e:
  328. logger.error(f"Error enrolling speaker {speaker_name}: {e}")
  329. return False
  330. def remove_speaker_by_id(self, speaker_id: str) -> bool:
  331. """
  332. Remove a speaker by ID.
  333. Args:
  334. speaker_id: Speaker ID to remove
  335. Returns:
  336. True if removal successful
  337. """
  338. try:
  339. # Get speaker info before removal
  340. speakers = self.voice_detector.get_speaker_list()
  341. target_speaker = next((s for s in speakers if s.speaker_id == speaker_id), None)
  342. if not target_speaker:
  343. logger.warning(f"Speaker {speaker_id} not found")
  344. return False
  345. speaker_name = target_speaker.speaker_name
  346. success = self.voice_detector.remove_speaker(speaker_id)
  347. if success:
  348. # Trigger removal event
  349. if EVENTS_AVAILABLE and self.event_handler:
  350. try:
  351. event_data = SpeakerRemovedEventData(
  352. speaker_id=speaker_id,
  353. speaker_name=speaker_name
  354. )
  355. self.event_handler.trigger_event("speaker_removed", event_data)
  356. except Exception as e:
  357. logger.error(f"Error triggering speaker removed event: {e}")
  358. logger.info(f"Successfully removed speaker: {speaker_name} ({speaker_id})")
  359. else:
  360. logger.warning(f"Failed to remove speaker: {speaker_id}")
  361. return success
  362. except Exception as e:
  363. logger.error(f"Error removing speaker {speaker_id}: {e}")
  364. return False
  365. def get_performance_statistics(self) -> Dict[str, Any]:
  366. """Get performance statistics for the integration."""
  367. stats = self._performance_stats.copy()
  368. # Add detector statistics
  369. try:
  370. detector_stats = self.voice_detector.get_performance_stats()
  371. stats.update(detector_stats)
  372. except Exception as e:
  373. logger.error(f"Error getting detector stats: {e}")
  374. # Calculate success rate
  375. if stats['total_recognitions'] > 0:
  376. stats['recognition_success_rate'] = stats['successful_recognitions'] / stats['total_recognitions']
  377. else:
  378. stats['recognition_success_rate'] = 0.0
  379. return stats
  380. def reset_statistics(self):
  381. """Reset performance statistics."""
  382. self._performance_stats = {
  383. 'total_recognitions': 0,
  384. 'successful_recognitions': 0,
  385. 'wakeword_enhancements': 0,
  386. 'average_processing_time_ms': 0.0,
  387. 'last_recognition': None
  388. }
  389. def _register_event_handlers(self):
  390. """Register event handlers with the event system."""
  391. if not EVENTS_AVAILABLE:
  392. return
  393. # Register handlers for wakeword and audio events
  394. try:
  395. # Note: In a real implementation, these would be proper event handler methods
  396. # with @TrixyEvent decorators
  397. logger.info("Event handlers registered for voice recognition integration")
  398. except Exception as e:
  399. logger.error(f"Error registering event handlers: {e}")
  400. def _continuous_recognition_loop(self):
  401. """Background loop for continuous recognition."""
  402. logger.info("Starting continuous recognition loop")
  403. # This is a simplified loop - in practice, you'd integrate with
  404. # the actual audio streaming system
  405. while not self._stop_event.is_set():
  406. try:
  407. # Simulate audio processing
  408. # In real implementation, this would process actual audio streams
  409. time.sleep(0.1) # 100ms processing interval
  410. except Exception as e:
  411. logger.error(f"Error in continuous recognition loop: {e}")
  412. time.sleep(1.0) # Back off on error
  413. logger.info("Continuous recognition loop stopped")
  414. def _update_performance_stats(self, result: SpeakerIdentificationResult):
  415. """Update performance statistics."""
  416. self._performance_stats['total_recognitions'] += 1
  417. if result.speaker_id:
  418. self._performance_stats['successful_recognitions'] += 1
  419. # Update average processing time
  420. current_avg = self._performance_stats['average_processing_time_ms']
  421. total_count = self._performance_stats['total_recognitions']
  422. new_avg = ((current_avg * (total_count - 1)) + result.processing_time_ms) / total_count
  423. self._performance_stats['average_processing_time_ms'] = new_avg
  424. self._performance_stats['last_recognition'] = datetime.now().isoformat()
  425. # Event handler methods (would use @TrixyEvent decorator in real implementation)
  426. def handle_wakeword_received(self, event_name: str, event_data: WakewordReceivedEventData):
  427. """Handle wakeword received events."""
  428. if not self.enable_wakeword_enhancement:
  429. return
  430. try:
  431. # Extract audio data from event
  432. # In real implementation, this would get audio from the event data
  433. logger.debug(f"Processing wakeword event for enhancement: {event_name}")
  434. # This would call process_wakeword_detection with actual audio data
  435. except Exception as e:
  436. logger.error(f"Error handling wakeword event: {e}")
  437. def handle_raw_audio_received(self, event_name: str, event_data: RawAudioInputReceivedEventData):
  438. """Handle raw audio input events."""
  439. if not self.enable_continuous_recognition:
  440. return
  441. try:
  442. # Process audio for speaker recognition
  443. # In real implementation, this would extract audio from event data
  444. logger.debug(f"Processing raw audio event: {event_name}")
  445. # This would call process_audio_stream with actual audio data
  446. except Exception as e:
  447. logger.error(f"Error handling raw audio event: {e}")
  448. def create_voice_recognition_integration(voice_detector: VoiceRecognitionDetector,
  449. event_handler: Optional[EventHandler] = None,
  450. **kwargs) -> VoiceRecognitionIntegration:
  451. """
  452. Factory function to create voice recognition integration.
  453. Args:
  454. voice_detector: Voice recognition detector instance
  455. event_handler: Event handler for system integration
  456. **kwargs: Additional configuration parameters
  457. Returns:
  458. Configured voice recognition integration
  459. """
  460. return VoiceRecognitionIntegration(
  461. voice_detector=voice_detector,
  462. event_handler=event_handler,
  463. **kwargs
  464. )
  465. def setup_complete_voice_recognition_system(config: VoiceRecognitionConfig,
  466. event_handler: Optional[EventHandler] = None,
  467. **kwargs) -> VoiceRecognitionIntegration:
  468. """
  469. Set up a complete voice recognition system with all components.
  470. Args:
  471. config: Voice recognition configuration
  472. event_handler: Event handler for system integration
  473. **kwargs: Additional configuration parameters
  474. Returns:
  475. Complete voice recognition integration system
  476. """
  477. try:
  478. from .detector import create_voice_recognition_detector
  479. # Create voice recognition detector
  480. detector = create_voice_recognition_detector(
  481. config=config,
  482. event_handler=event_handler
  483. )
  484. # Create integration
  485. integration = create_voice_recognition_integration(
  486. voice_detector=detector,
  487. event_handler=event_handler,
  488. **kwargs
  489. )
  490. logger.info("Complete voice recognition system created successfully")
  491. return integration
  492. except Exception as e:
  493. logger.error(f"Failed to create complete voice recognition system: {e}")
  494. raise
  495. # Example usage and testing
  496. def test_voice_recognition_integration():
  497. """Test voice recognition integration functionality."""
  498. try:
  499. from .config import create_default_config
  500. from .detector import create_voice_recognition_detector
  501. # Create mock event handler if events not available
  502. if EVENTS_AVAILABLE:
  503. from trixy_core.events import create_default_event_handler
  504. event_handler = create_default_event_handler()
  505. else:
  506. event_handler = None
  507. # Create configuration
  508. config = create_default_config("/tmp/test_model.pth")
  509. # Create detector
  510. detector = create_voice_recognition_detector(config, event_handler)
  511. # Create integration
  512. integration = create_voice_recognition_integration(
  513. detector, event_handler, enable_wakeword_enhancement=True
  514. )
  515. print("Testing voice recognition integration...")
  516. # Start integration
  517. integration.start()
  518. # Test wakeword enhancement
  519. test_audio = np.random.randn(16000 * 2).astype(np.float32) # 2 seconds of audio
  520. enhanced_info = integration.process_wakeword_detection(
  521. test_audio, "custom_wakeword"
  522. )
  523. print(f"Enhanced wakeword info: {enhanced_info}")
  524. # Test speaker enrollment
  525. enrollment_samples = [np.random.randn(16000 * 3).astype(np.float32) for _ in range(3)]
  526. success = integration.enroll_speaker_from_audio("Test Speaker", enrollment_samples)
  527. print(f"Speaker enrollment success: {success}")
  528. # Get statistics
  529. stats = integration.get_performance_statistics()
  530. print(f"Performance statistics: {stats}")
  531. # Stop integration
  532. integration.stop()
  533. print("Voice recognition integration test completed")
  534. except Exception as e:
  535. print(f"Integration test failed: {e}")
  536. import traceback
  537. traceback.print_exc()
  538. if __name__ == "__main__":
  539. test_voice_recognition_integration()