Spaces:
Sleeping
Sleeping
| from typing import Dict, Optional | |
| from dataclasses import dataclass, field | |
| from datetime import datetime, timezone | |
| class MicrophoneState: | |
| mic_id: str | |
| name: str | |
| priority: int = 5 # 1–10 | |
| is_chairman: bool = False | |
| is_muted: bool = False | |
| is_speech_active: bool = False | |
| db_level: float = -100.0 | |
| speech_probability: float = 0.0 | |
| last_speech_time: Optional[datetime] = field(default=None) | |
| gain: float = 1.0 | |
| class PriorityManager: | |
| """ | |
| Manages microphone priorities and speech-collision prevention. | |
| When multiple microphones detect speech simultaneously: | |
| - Chairman microphone always wins. | |
| - Otherwise the highest-priority (then loudest) microphone wins and all | |
| others are flagged for suppression. | |
| """ | |
| def __init__(self): | |
| self._microphones: Dict[str, MicrophoneState] = {} | |
| self._active_speaker: Optional[str] = None | |
| # ------------------------------------------------------------------ | |
| # Microphone registration | |
| # ------------------------------------------------------------------ | |
| def register_microphone( | |
| self, | |
| mic_id: str, | |
| name: str, | |
| priority: int = 5, | |
| is_chairman: bool = False, | |
| ) -> MicrophoneState: | |
| state = MicrophoneState( | |
| mic_id=mic_id, | |
| name=name, | |
| priority=priority, | |
| is_chairman=is_chairman, | |
| ) | |
| self._microphones[mic_id] = state | |
| return state | |
| def remove_microphone(self, mic_id: str) -> None: | |
| self._microphones.pop(mic_id, None) | |
| if self._active_speaker == mic_id: | |
| self._active_speaker = None | |
| # ------------------------------------------------------------------ | |
| # State updates | |
| # ------------------------------------------------------------------ | |
| def update_state(self, mic_id: str, vad_result: dict, gain: float) -> None: | |
| """Apply a VAD result to the stored microphone state.""" | |
| if mic_id not in self._microphones: | |
| return | |
| mic = self._microphones[mic_id] | |
| mic.db_level = vad_result.get("db_level", -100.0) | |
| mic.speech_probability = vad_result.get("speech_probability", 0.0) | |
| mic.is_speech_active = vad_result.get("is_speech", False) | |
| mic.gain = gain | |
| if mic.is_speech_active: | |
| mic.last_speech_time = datetime.now(timezone.utc) | |
| # ------------------------------------------------------------------ | |
| # Collision resolution | |
| # ------------------------------------------------------------------ | |
| def resolve_collisions(self) -> Dict[str, bool]: | |
| """ | |
| Determine which microphones should be suppressed due to collision. | |
| Returns {mic_id: should_suppress} for every registered microphone. | |
| """ | |
| active_speakers = [ | |
| mic | |
| for mic in self._microphones.values() | |
| if mic.is_speech_active and not mic.is_muted | |
| ] | |
| # No collision with 0 or 1 active speaker | |
| if len(active_speakers) <= 1: | |
| if active_speakers: | |
| self._active_speaker = active_speakers[0].mic_id | |
| return {mid: False for mid in self._microphones} | |
| # Chairman always wins | |
| chairman_speaking = any(m.is_chairman for m in active_speakers) | |
| if chairman_speaking: | |
| chairman_id = next( | |
| m.mic_id for m in active_speakers if m.is_chairman | |
| ) | |
| self._active_speaker = chairman_id | |
| return { | |
| mid: (mic.is_speech_active and not mic.is_chairman and not mic.is_muted) | |
| for mid, mic in self._microphones.items() | |
| } | |
| # No chairman — highest priority (then loudest) wins | |
| active_speakers.sort(key=lambda m: (-m.priority, -m.db_level)) | |
| winner = active_speakers[0] | |
| self._active_speaker = winner.mic_id | |
| return { | |
| mid: (mic.is_speech_active and mic.mic_id != winner.mic_id and not mic.is_muted) | |
| for mid, mic in self._microphones.items() | |
| } | |
| # ------------------------------------------------------------------ | |
| # Accessors / mutators | |
| # ------------------------------------------------------------------ | |
| def get_active_speaker(self) -> Optional[str]: | |
| return self._active_speaker | |
| def get_all_states(self) -> Dict[str, dict]: | |
| return { | |
| mid: { | |
| "mic_id": mic.mic_id, | |
| "name": mic.name, | |
| "priority": mic.priority, | |
| "is_chairman": mic.is_chairman, | |
| "is_muted": mic.is_muted, | |
| "is_speech_active": mic.is_speech_active, | |
| "db_level": mic.db_level, | |
| "speech_probability": mic.speech_probability, | |
| "gain": mic.gain, | |
| "last_speech_time": ( | |
| mic.last_speech_time.isoformat() | |
| if mic.last_speech_time | |
| else None | |
| ), | |
| } | |
| for mid, mic in self._microphones.items() | |
| } | |
| def set_chairman(self, mic_id: str) -> None: | |
| """Designate a single microphone as chairman; demote all others.""" | |
| for mic in self._microphones.values(): | |
| mic.is_chairman = mic.mic_id == mic_id | |
| def set_muted(self, mic_id: str, muted: bool) -> None: | |
| if mic_id in self._microphones: | |
| self._microphones[mic_id].is_muted = muted | |
| def set_priority(self, mic_id: str, priority: int) -> None: | |
| if mic_id in self._microphones: | |
| self._microphones[mic_id].priority = max(1, min(10, priority)) | |