diff --git a/backend/main.py b/backend/main.py index 7c4aa7e..22e8053 100644 --- a/backend/main.py +++ b/backend/main.py @@ -436,6 +436,7 @@ class AudioDeviceSettings(BaseModel): input_channel: Optional[int] = None output_device: Optional[int] = None caller_channel: Optional[int] = None + live_caller_channel: Optional[int] = None music_channel: Optional[int] = None sfx_channel: Optional[int] = None phone_filter: Optional[bool] = None @@ -471,6 +472,7 @@ async def set_audio_settings(settings: AudioDeviceSettings): input_channel=settings.input_channel, output_device=settings.output_device, caller_channel=settings.caller_channel, + live_caller_channel=settings.live_caller_channel, music_channel=settings.music_channel, sfx_channel=settings.sfx_channel, phone_filter=settings.phone_filter @@ -832,9 +834,8 @@ async def caller_audio_stream(websocket: WebSocket): pcm_data = message["bytes"] audio_buffer.extend(pcm_data) - # Route to Loopback channel - channel = call_info["channel"] - audio_service.route_real_caller_audio(pcm_data, channel, SAMPLE_RATE) + # Route to configured live caller Loopback channel + audio_service.route_real_caller_audio(pcm_data, SAMPLE_RATE) # Transcribe when we have enough audio if len(audio_buffer) >= chunk_samples * 2: diff --git a/backend/services/audio.py b/backend/services/audio.py index 5cb786e..97d2c56 100644 --- a/backend/services/audio.py +++ b/backend/services/audio.py @@ -24,6 +24,7 @@ class AudioService: self.output_device: Optional[int] = None # Single output device (multi-channel) self.caller_channel: int = 1 # Channel for caller TTS + self.live_caller_channel: int = 4 # Channel for live caller audio self.music_channel: int = 2 # Channel for music self.sfx_channel: int = 3 # Channel for SFX self.phone_filter: bool = False # Phone filter on caller voices @@ -69,10 +70,11 @@ class AudioService: self.input_channel = data.get("input_channel", 1) self.output_device = data.get("output_device") self.caller_channel = data.get("caller_channel", 1) + self.live_caller_channel = data.get("live_caller_channel", 4) self.music_channel = data.get("music_channel", 2) self.sfx_channel = data.get("sfx_channel", 3) self.phone_filter = data.get("phone_filter", False) - print(f"Loaded audio settings: output={self.output_device}, channels={self.caller_channel}/{self.music_channel}/{self.sfx_channel}, phone_filter={self.phone_filter}") + print(f"Loaded audio settings: output={self.output_device}, channels={self.caller_channel}/{self.live_caller_channel}/{self.music_channel}/{self.sfx_channel}, phone_filter={self.phone_filter}") except Exception as e: print(f"Failed to load audio settings: {e}") @@ -84,6 +86,7 @@ class AudioService: "input_channel": self.input_channel, "output_device": self.output_device, "caller_channel": self.caller_channel, + "live_caller_channel": self.live_caller_channel, "music_channel": self.music_channel, "sfx_channel": self.sfx_channel, "phone_filter": self.phone_filter, @@ -114,6 +117,7 @@ class AudioService: input_channel: Optional[int] = None, output_device: Optional[int] = None, caller_channel: Optional[int] = None, + live_caller_channel: Optional[int] = None, music_channel: Optional[int] = None, sfx_channel: Optional[int] = None, phone_filter: Optional[bool] = None @@ -127,6 +131,8 @@ class AudioService: self.output_device = output_device if caller_channel is not None: self.caller_channel = caller_channel + if live_caller_channel is not None: + self.live_caller_channel = live_caller_channel if music_channel is not None: self.music_channel = music_channel if sfx_channel is not None: @@ -144,6 +150,7 @@ class AudioService: "input_channel": self.input_channel, "output_device": self.output_device, "caller_channel": self.caller_channel, + "live_caller_channel": self.live_caller_channel, "music_channel": self.music_channel, "sfx_channel": self.sfx_channel, "phone_filter": self.phone_filter, @@ -313,8 +320,8 @@ class AudioService: """Stop any playing caller audio""" self._caller_stop_event.set() - def route_real_caller_audio(self, pcm_data: bytes, channel: int, sample_rate: int): - """Route real caller PCM audio to a specific Loopback channel""" + def route_real_caller_audio(self, pcm_data: bytes, sample_rate: int): + """Route real caller PCM audio to the configured live caller Loopback channel""" import librosa if self.output_device is None: @@ -327,7 +334,7 @@ class AudioService: device_info = sd.query_devices(self.output_device) num_channels = device_info['max_output_channels'] device_sr = int(device_info['default_samplerate']) - channel_idx = min(channel, num_channels) - 1 + channel_idx = min(self.live_caller_channel, num_channels) - 1 # Resample to device sample rate if needed if sample_rate != device_sr: diff --git a/frontend/index.html b/frontend/index.html index e90f293..8f9e2a7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -128,6 +128,7 @@
+
@@ -206,6 +207,6 @@ - + diff --git a/frontend/js/app.js b/frontend/js/app.js index a883053..06aeac3 100644 --- a/frontend/js/app.js +++ b/frontend/js/app.js @@ -223,11 +223,13 @@ async function loadAudioDevices() { // Channel settings const inputCh = document.getElementById('input-channel'); const callerCh = document.getElementById('caller-channel'); + const liveCallerCh = document.getElementById('live-caller-channel'); const musicCh = document.getElementById('music-channel'); const sfxCh = document.getElementById('sfx-channel'); if (inputCh) inputCh.value = settings.input_channel || 1; if (callerCh) callerCh.value = settings.caller_channel || 1; + if (liveCallerCh) liveCallerCh.value = settings.live_caller_channel || 4; if (musicCh) musicCh.value = settings.music_channel || 2; if (sfxCh) sfxCh.value = settings.sfx_channel || 3; @@ -250,6 +252,7 @@ async function saveAudioDevices() { const outputDevice = document.getElementById('output-device')?.value; const inputChannel = document.getElementById('input-channel')?.value; const callerChannel = document.getElementById('caller-channel')?.value; + const liveCallerChannel = document.getElementById('live-caller-channel')?.value; const musicChannel = document.getElementById('music-channel')?.value; const sfxChannel = document.getElementById('sfx-channel')?.value; const phoneFilterChecked = document.getElementById('phone-filter')?.checked ?? false; @@ -262,6 +265,7 @@ async function saveAudioDevices() { input_channel: inputChannel ? parseInt(inputChannel) : 1, output_device: outputDevice ? parseInt(outputDevice) : null, caller_channel: callerChannel ? parseInt(callerChannel) : 1, + live_caller_channel: liveCallerChannel ? parseInt(liveCallerChannel) : 4, music_channel: musicChannel ? parseInt(musicChannel) : 2, sfx_channel: sfxChannel ? parseInt(sfxChannel) : 3, phone_filter: phoneFilterChecked