2026-06-19 02:10:25 +02:00
|
|
|
import SwiftUI
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
import AVKit
|
2026-06-19 02:10:25 +02:00
|
|
|
import VoiceCatCore
|
|
|
|
|
|
|
|
|
|
struct SettingsView: View {
|
|
|
|
|
@Environment(AppState.self) private var appState
|
|
|
|
|
@Bindable var session: SessionState
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
@StateObject private var router = IOSAudioRouter.shared
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
@State private var showAdvanced = false
|
2026-06-19 02:10:25 +02:00
|
|
|
|
feat(clients): event sound effects + optional text-to-speech
Add audible cues and optional spoken announcements for session events
(join/leave, channel + PM sent/recv, login, logout/connection-lost,
mic on/off, voice-activity, PTT) across all three clients, driven off
the shared C ABI vc_event stream so the mapping stays consistent.
TTS is off by default; when enabled it announces events and reads
message/PM bodies aloud. Master toggles + a sound-volume slider; the
per-utterance voice-activity and PTT cues default off. WAVs ship from
assets/sounds/.
Windows (built + verified): new VoiceCat.App/Notifications/ layer
(FeedbackSettings -> %AppData%\VoiceCat\feedback.json, SoundPlayerPool
via System.Media.SoundPlayer, SpeechAnnouncer via Prismatoid 0.3.0,
EventFeedback dispatcher); MainForm hooks; NotificationSettingsForm
under Settings > Notifications; csproj adds the Prismatoid PackageRef
and copies the WAVs into sounds\.
macOS + iOS (written, not yet built -- needs a Mac): shared
VoiceCatCore/Feedback/ (SoundEvent, EventFeedback = AVAudioPlayer pool
+ native AVSpeechSynthesizer, FeedbackSettings over UserDefaults); WAVs
bundled via Package.swift resources (.process). Hooks in SessionState/
AppState (iOS) and MainWindowController (macOS); settings UI in
SettingsView (iOS) and SettingsWindowController (macOS).
No core/server code touched; ctest --preset dev unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:20:11 +02:00
|
|
|
// Notification feedback prefs — keys shared with VoiceCatCore's FeedbackSettings, so the
|
|
|
|
|
// EventFeedback player reads the same values these toggles write.
|
|
|
|
|
@AppStorage("feedback.sounds") private var soundsEnabled = true
|
|
|
|
|
@AppStorage("feedback.speech") private var speechEnabled = false
|
|
|
|
|
@AppStorage("feedback.volume") private var soundsVolume = 1.0
|
|
|
|
|
@AppStorage("feedback.selfTalk") private var selfTalkEnabled = false
|
|
|
|
|
@AppStorage("feedback.ptt") private var pttSoundEnabled = false
|
|
|
|
|
|
2026-06-19 02:10:25 +02:00
|
|
|
var body: some View {
|
|
|
|
|
NavigationStack {
|
|
|
|
|
Form {
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
// MARK: - Audio Preset
|
|
|
|
|
Section("Audio") {
|
|
|
|
|
Picker("Preset", selection: Binding(
|
|
|
|
|
get: { router.activePreset },
|
|
|
|
|
set: { preset in router.applyPreset(preset) }
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
)) {
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
ForEach(router.availablePresets) { preset in
|
|
|
|
|
Text(preset.rawValue).tag(preset)
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
.accessibilityLabel("Audio preset")
|
|
|
|
|
|
2026-06-21 15:47:20 +02:00
|
|
|
Toggle("Speaker output", isOn: Binding(
|
|
|
|
|
get: { router.forceSpeaker },
|
|
|
|
|
set: { router.setForceSpeaker($0) }
|
|
|
|
|
))
|
|
|
|
|
.accessibilityLabel("Speaker output")
|
|
|
|
|
.accessibilityHint("Routes audio to the speaker instead of the earpiece when no headphones are connected.")
|
|
|
|
|
|
fix(ios-audio): unify iOS audio onto one always-external AVAudioEngine
The iOS audio path was a hybrid: Voice-Chat-class presets ran a native
VPIO AVAudioEngine (core external) while Stereo/Studio/A2DP presets ran
the core's miniaudio devices. Nearly every "no input / no output / both"
bug lived in the seam between the two paths — the lingering miniaudio
capture unit fighting VPIO, the audioRestart ordering dance, the
route-change "glitching" loop, stereo<->mono stickiness, and
"can't hear anyone". Switching presets/routes mid-call routinely dropped
a direction.
Drive ALL iOS audio through one AVAudioEngine with the core fully
external at all times: setExternalPlayback(1) once at connect, every MIC
stream external_feed=1, mic via vc_stream_feed_pcm, playback via
vc_set_mixed_output_sink (drained by an always-on AVAudioSourceNode so
remote audio plays before joining voice). VPIO + AGC toggle per preset.
Every preset/route/interruption change funnels through one deterministic
Swift-only reconfigure (stop -> apply session config -> rebuild -> start)
— no second path to hand off to, so a change can't drop a direction.
- IOSVoiceProcessingEngine.swift -> IOSAudioEngine: always-on source-node
playback, conditional mic tap, VPIO/AGC; one rebuild() backing
startListening/stop/startMic/stopMic/reconfigure/setCaptureChannels.
- IOSAudioRouter: 7 presets -> 4 (Voice Chat / Stereo Mic / Mono Mic /
Advanced); persisted voiceProcessingEnabled + agcEnabled; setters call
IOSAudioEngine.reconfigure() instead of audioRestart/reconcileVoicePath.
- AudioSessionManager slimmed; SessionState mic lifecycle collapsed;
AppState wires external playback + listening at connect, stop at
disconnect; SettingsView shows 4 presets + Advanced VPIO/AGC toggles.
No core/ABI/test changes — relies on the already-shipped external API
(test_external_pcm, test_external_playback). xcodebuild iOS device Debug
BUILD SUCCEEDED. Updates docs/voice.md §8 and PROGRESS.md.
2026-06-23 02:45:53 +02:00
|
|
|
// Surface the voice-processing state. On Voice Chat the native iOS
|
2026-06-22 02:38:01 +02:00
|
|
|
// Voice-Processing unit (VPIO) does echo cancellation, noise suppression and
|
fix(ios-audio): unify iOS audio onto one always-external AVAudioEngine
The iOS audio path was a hybrid: Voice-Chat-class presets ran a native
VPIO AVAudioEngine (core external) while Stereo/Studio/A2DP presets ran
the core's miniaudio devices. Nearly every "no input / no output / both"
bug lived in the seam between the two paths — the lingering miniaudio
capture unit fighting VPIO, the audioRestart ordering dance, the
route-change "glitching" loop, stereo<->mono stickiness, and
"can't hear anyone". Switching presets/routes mid-call routinely dropped
a direction.
Drive ALL iOS audio through one AVAudioEngine with the core fully
external at all times: setExternalPlayback(1) once at connect, every MIC
stream external_feed=1, mic via vc_stream_feed_pcm, playback via
vc_set_mixed_output_sink (drained by an always-on AVAudioSourceNode so
remote audio plays before joining voice). VPIO + AGC toggle per preset.
Every preset/route/interruption change funnels through one deterministic
Swift-only reconfigure (stop -> apply session config -> rebuild -> start)
— no second path to hand off to, so a change can't drop a direction.
- IOSVoiceProcessingEngine.swift -> IOSAudioEngine: always-on source-node
playback, conditional mic tap, VPIO/AGC; one rebuild() backing
startListening/stop/startMic/stopMic/reconfigure/setCaptureChannels.
- IOSAudioRouter: 7 presets -> 4 (Voice Chat / Stereo Mic / Mono Mic /
Advanced); persisted voiceProcessingEnabled + agcEnabled; setters call
IOSAudioEngine.reconfigure() instead of audioRestart/reconcileVoicePath.
- AudioSessionManager slimmed; SessionState mic lifecycle collapsed;
AppState wires external playback + listening at connect, stop at
disconnect; SettingsView shows 4 presets + Advanced VPIO/AGC toggles.
No core/ABI/test changes — relies on the already-shipped external API
(test_external_pcm, test_external_playback). xcodebuild iOS device Debug
BUILD SUCCEEDED. Updates docs/voice.md §8 and PROGRESS.md.
2026-06-23 02:45:53 +02:00
|
|
|
// automatic gain control; the stereo / mono-mic / A2DP configs can't use it.
|
2026-06-22 02:38:01 +02:00
|
|
|
if router.currentConfigUsesVoiceProcessing {
|
|
|
|
|
Label("Echo cancellation & noise suppression on (iOS voice processing)",
|
|
|
|
|
systemImage: "waveform.badge.mic")
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.accessibilityLabel("Echo cancellation and noise suppression are on")
|
|
|
|
|
} else {
|
fix(ios-audio): unify iOS audio onto one always-external AVAudioEngine
The iOS audio path was a hybrid: Voice-Chat-class presets ran a native
VPIO AVAudioEngine (core external) while Stereo/Studio/A2DP presets ran
the core's miniaudio devices. Nearly every "no input / no output / both"
bug lived in the seam between the two paths — the lingering miniaudio
capture unit fighting VPIO, the audioRestart ordering dance, the
route-change "glitching" loop, stereo<->mono stickiness, and
"can't hear anyone". Switching presets/routes mid-call routinely dropped
a direction.
Drive ALL iOS audio through one AVAudioEngine with the core fully
external at all times: setExternalPlayback(1) once at connect, every MIC
stream external_feed=1, mic via vc_stream_feed_pcm, playback via
vc_set_mixed_output_sink (drained by an always-on AVAudioSourceNode so
remote audio plays before joining voice). VPIO + AGC toggle per preset.
Every preset/route/interruption change funnels through one deterministic
Swift-only reconfigure (stop -> apply session config -> rebuild -> start)
— no second path to hand off to, so a change can't drop a direction.
- IOSVoiceProcessingEngine.swift -> IOSAudioEngine: always-on source-node
playback, conditional mic tap, VPIO/AGC; one rebuild() backing
startListening/stop/startMic/stopMic/reconfigure/setCaptureChannels.
- IOSAudioRouter: 7 presets -> 4 (Voice Chat / Stereo Mic / Mono Mic /
Advanced); persisted voiceProcessingEnabled + agcEnabled; setters call
IOSAudioEngine.reconfigure() instead of audioRestart/reconcileVoicePath.
- AudioSessionManager slimmed; SessionState mic lifecycle collapsed;
AppState wires external playback + listening at connect, stop at
disconnect; SettingsView shows 4 presets + Advanced VPIO/AGC toggles.
No core/ABI/test changes — relies on the already-shipped external API
(test_external_pcm, test_external_playback). xcodebuild iOS device Debug
BUILD SUCCEEDED. Updates docs/voice.md §8 and PROGRESS.md.
2026-06-23 02:45:53 +02:00
|
|
|
Label("No echo cancellation in this configuration (stereo / A2DP / off)",
|
2026-06-22 02:38:01 +02:00
|
|
|
systemImage: "waveform.slash")
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundStyle(.secondary)
|
fix(ios-audio): unify iOS audio onto one always-external AVAudioEngine
The iOS audio path was a hybrid: Voice-Chat-class presets ran a native
VPIO AVAudioEngine (core external) while Stereo/Studio/A2DP presets ran
the core's miniaudio devices. Nearly every "no input / no output / both"
bug lived in the seam between the two paths — the lingering miniaudio
capture unit fighting VPIO, the audioRestart ordering dance, the
route-change "glitching" loop, stereo<->mono stickiness, and
"can't hear anyone". Switching presets/routes mid-call routinely dropped
a direction.
Drive ALL iOS audio through one AVAudioEngine with the core fully
external at all times: setExternalPlayback(1) once at connect, every MIC
stream external_feed=1, mic via vc_stream_feed_pcm, playback via
vc_set_mixed_output_sink (drained by an always-on AVAudioSourceNode so
remote audio plays before joining voice). VPIO + AGC toggle per preset.
Every preset/route/interruption change funnels through one deterministic
Swift-only reconfigure (stop -> apply session config -> rebuild -> start)
— no second path to hand off to, so a change can't drop a direction.
- IOSVoiceProcessingEngine.swift -> IOSAudioEngine: always-on source-node
playback, conditional mic tap, VPIO/AGC; one rebuild() backing
startListening/stop/startMic/stopMic/reconfigure/setCaptureChannels.
- IOSAudioRouter: 7 presets -> 4 (Voice Chat / Stereo Mic / Mono Mic /
Advanced); persisted voiceProcessingEnabled + agcEnabled; setters call
IOSAudioEngine.reconfigure() instead of audioRestart/reconcileVoicePath.
- AudioSessionManager slimmed; SessionState mic lifecycle collapsed;
AppState wires external playback + listening at connect, stop at
disconnect; SettingsView shows 4 presets + Advanced VPIO/AGC toggles.
No core/ABI/test changes — relies on the already-shipped external API
(test_external_pcm, test_external_playback). xcodebuild iOS device Debug
BUILD SUCCEEDED. Updates docs/voice.md §8 and PROGRESS.md.
2026-06-23 02:45:53 +02:00
|
|
|
.accessibilityLabel("Echo cancellation is off in this configuration")
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
}
|
|
|
|
|
}
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
// MARK: - Advanced Audio
|
|
|
|
|
Section {
|
|
|
|
|
DisclosureGroup("Advanced Audio", isExpanded: $showAdvanced) {
|
|
|
|
|
// Input port picker (AVAudioSession.availableInputs)
|
|
|
|
|
Picker("Input Port", selection: Binding(
|
|
|
|
|
get: { router.selectedInputPortId ?? "" },
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
set: { id in
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
if !id.isEmpty { router.selectInputPort(id) }
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
|
|
|
|
)) {
|
|
|
|
|
Text("Default").tag("")
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
ForEach(router.inputPorts) { port in
|
|
|
|
|
Text(port.name).tag(port.id)
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
.accessibilityLabel("Audio input port selection")
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
// Built-in mic sub-options: orientation (data source) + polar pattern
|
|
|
|
|
if router.selectedPortIsBuiltInMic,
|
|
|
|
|
let dataSources = router.selectedPortDataSources,
|
|
|
|
|
!dataSources.isEmpty {
|
|
|
|
|
Picker("Mic Orientation", selection: Binding(
|
|
|
|
|
get: { router.selectedDataSourceId ?? "" },
|
|
|
|
|
set: { id in
|
|
|
|
|
if !id.isEmpty { router.selectDataSource(id) }
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
|
|
|
|
)) {
|
|
|
|
|
Text("Default").tag("")
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
ForEach(dataSources) { ds in
|
|
|
|
|
Text(ds.name).tag(ds.id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.accessibilityLabel("Microphone orientation")
|
|
|
|
|
|
|
|
|
|
// Polar pattern sub-picker
|
|
|
|
|
if let selectedDs = dataSources.first(where: { $0.id == router.selectedDataSourceId }),
|
|
|
|
|
let patterns = selectedDs.polarPatterns,
|
|
|
|
|
!patterns.isEmpty {
|
|
|
|
|
Picker("Polar Pattern", selection: Binding(
|
|
|
|
|
get: { router.selectedPolarPattern ?? "" },
|
|
|
|
|
set: { pattern in
|
|
|
|
|
if !pattern.isEmpty { router.selectPolarPattern(pattern) }
|
|
|
|
|
}
|
|
|
|
|
)) {
|
|
|
|
|
Text("Default").tag("")
|
|
|
|
|
ForEach(patterns, id: \.self) { pattern in
|
|
|
|
|
Text(polarPatternLabel(pattern)).tag(pattern)
|
|
|
|
|
}
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
.accessibilityLabel("Microphone polar pattern")
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
// Mic processing mode: Standard vs Raw/Studio
|
|
|
|
|
Picker("Mic Mode", selection: Binding(
|
|
|
|
|
get: { router.micMode },
|
|
|
|
|
set: { router.selectMicMode($0) }
|
|
|
|
|
)) {
|
|
|
|
|
ForEach(IOSAudioRouter.MicMode.allCases) { mode in
|
|
|
|
|
Text(mode.rawValue).tag(mode)
|
|
|
|
|
}
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
.accessibilityLabel("Microphone processing mode")
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
|
fix(ios-audio): unify iOS audio onto one always-external AVAudioEngine
The iOS audio path was a hybrid: Voice-Chat-class presets ran a native
VPIO AVAudioEngine (core external) while Stereo/Studio/A2DP presets ran
the core's miniaudio devices. Nearly every "no input / no output / both"
bug lived in the seam between the two paths — the lingering miniaudio
capture unit fighting VPIO, the audioRestart ordering dance, the
route-change "glitching" loop, stereo<->mono stickiness, and
"can't hear anyone". Switching presets/routes mid-call routinely dropped
a direction.
Drive ALL iOS audio through one AVAudioEngine with the core fully
external at all times: setExternalPlayback(1) once at connect, every MIC
stream external_feed=1, mic via vc_stream_feed_pcm, playback via
vc_set_mixed_output_sink (drained by an always-on AVAudioSourceNode so
remote audio plays before joining voice). VPIO + AGC toggle per preset.
Every preset/route/interruption change funnels through one deterministic
Swift-only reconfigure (stop -> apply session config -> rebuild -> start)
— no second path to hand off to, so a change can't drop a direction.
- IOSVoiceProcessingEngine.swift -> IOSAudioEngine: always-on source-node
playback, conditional mic tap, VPIO/AGC; one rebuild() backing
startListening/stop/startMic/stopMic/reconfigure/setCaptureChannels.
- IOSAudioRouter: 7 presets -> 4 (Voice Chat / Stereo Mic / Mono Mic /
Advanced); persisted voiceProcessingEnabled + agcEnabled; setters call
IOSAudioEngine.reconfigure() instead of audioRestart/reconcileVoicePath.
- AudioSessionManager slimmed; SessionState mic lifecycle collapsed;
AppState wires external playback + listening at connect, stop at
disconnect; SettingsView shows 4 presets + Advanced VPIO/AGC toggles.
No core/ABI/test changes — relies on the already-shipped external API
(test_external_pcm, test_external_playback). xcodebuild iOS device Debug
BUILD SUCCEEDED. Updates docs/voice.md §8 and PROGRESS.md.
2026-06-23 02:45:53 +02:00
|
|
|
// Voice-processing (VPIO) controls — only meaningful on a VPIO-capable
|
|
|
|
|
// config (mono + standard + non-A2DP). iOS bundles echo cancellation and
|
|
|
|
|
// noise suppression into one master switch (no per-stage toggle); AGC is
|
|
|
|
|
// the one sub-stage it lets us control independently.
|
|
|
|
|
if router.voiceProcessingAvailable {
|
|
|
|
|
Toggle("Voice Processing (AEC + noise suppression)", isOn: Binding(
|
|
|
|
|
get: { router.voiceProcessingEnabled },
|
|
|
|
|
set: { router.setVoiceProcessingEnabled($0) }
|
|
|
|
|
))
|
|
|
|
|
.accessibilityLabel("Voice processing")
|
|
|
|
|
.accessibilityHint("Echo cancellation and noise suppression, bundled together by iOS.")
|
|
|
|
|
|
|
|
|
|
if router.voiceProcessingEnabled {
|
|
|
|
|
Toggle("Automatic Gain Control", isOn: Binding(
|
|
|
|
|
get: { router.agcEnabled },
|
|
|
|
|
set: { router.setAgcEnabled($0) }
|
|
|
|
|
))
|
|
|
|
|
.accessibilityLabel("Automatic gain control")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
if router.showsRawModeSpeakerWarning {
|
|
|
|
|
Label(
|
|
|
|
|
"Raw mode on speaker — echo risk (no AEC)",
|
|
|
|
|
systemImage: "exclamationmark.triangle.fill"
|
|
|
|
|
)
|
|
|
|
|
.foregroundStyle(.orange)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.accessibilityLabel("Warning: Raw mode with speaker output may cause echo")
|
|
|
|
|
}
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
if router.showsA2dpNoAecWarning {
|
|
|
|
|
Label(
|
|
|
|
|
"A2DP mode — no echo cancellation (hardware AEC unavailable)",
|
|
|
|
|
systemImage: "info.circle.fill"
|
|
|
|
|
)
|
|
|
|
|
.foregroundStyle(.blue)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.accessibilityLabel("Info: A2DP output mode does not support hardware echo cancellation")
|
|
|
|
|
}
|
2026-06-19 13:46:20 +02:00
|
|
|
|
2026-06-19 16:58:21 +02:00
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
// Capture channels: Mono vs Stereo
|
|
|
|
|
Picker("Channels", selection: Binding(
|
|
|
|
|
get: { router.captureChannels },
|
|
|
|
|
set: { router.selectCaptureChannels($0) }
|
|
|
|
|
)) {
|
|
|
|
|
ForEach(IOSAudioRouter.CaptureChannels.allCases) { ch in
|
|
|
|
|
Text(ch.rawValue).tag(ch)
|
|
|
|
|
}
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
.accessibilityLabel("Capture channel count")
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
// Bluetooth mode
|
|
|
|
|
Picker("Bluetooth Mode", selection: Binding(
|
|
|
|
|
get: { router.bluetoothMode },
|
|
|
|
|
set: { router.selectBluetoothMode($0) }
|
|
|
|
|
)) {
|
|
|
|
|
ForEach(IOSAudioRouter.BluetoothMode.allCases) { mode in
|
|
|
|
|
Text(mode.rawValue).tag(mode)
|
|
|
|
|
}
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
.accessibilityLabel("Bluetooth audio mode")
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
// Current output route (read-only)
|
|
|
|
|
if !router.outputRoutes.isEmpty {
|
|
|
|
|
ForEach(router.outputRoutes) { route in
|
|
|
|
|
HStack {
|
|
|
|
|
Text(route.name)
|
|
|
|
|
Spacer()
|
|
|
|
|
Text(route.portType)
|
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
}
|
|
|
|
|
.accessibilityLabel("Current output: \(route.name)")
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
} else {
|
|
|
|
|
Text("No output route")
|
|
|
|
|
.foregroundStyle(.secondary)
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
|
|
|
|
|
feat(ios): audio presets + advanced settings disclosure
Replaces the flat list of audio settings with a preset picker that shows
context-appropriate options based on whether a Bluetooth device is connected.
Presets:
- Default (Phone Speaker): built-in mic + speaker, standard, mono
- Bluetooth Headset (HFP): BT mic + BT output, standard, mono — only shown
when a BT device is connected
- BT Headphones + Phone Mic: A2DP stereo output + built-in mic, standard,
mono — only shown when BT connected
- BT Headphones + Stereo Mic: A2DP stereo output + built-in mic stereo
(front+back capsules), standard, stereo — only shown when BT connected
- Custom: shown when advanced settings don't match any preset
When no Bluetooth device is connected, only 'Default' and 'Custom' appear,
with a hint to connect Bluetooth headphones for more options.
All granular controls (input port, orientation, polar pattern, mic mode,
channels, bluetooth mode, output route, AirPlay) are now under an
'Advanced Audio' disclosure group, collapsed by default.
IOSAudioRouter gains:
- AudioPreset enum with bluetoothMode/captureChannels/micMode/usesBuiltInMic
- hasBluetoothDevice detection (checks currentRoute + availableInputs for
bluetoothA2DP/bluetoothHFP port types)
- availablePresets (filtered by BT connection state)
- activePreset (computed from current settings)
- applyPreset() (sets all individual settings + finds built-in mic port UID)
2026-06-19 14:05:19 +02:00
|
|
|
// AirPlay button
|
|
|
|
|
HStack {
|
|
|
|
|
Text("AirPlay")
|
|
|
|
|
Spacer()
|
|
|
|
|
RoutePickerButton()
|
|
|
|
|
}
|
|
|
|
|
.accessibilityLabel("AirPlay output selector")
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: - Voice
|
|
|
|
|
Section("Voice") {
|
2026-06-19 02:10:25 +02:00
|
|
|
Picker("Input Mode", selection: Binding(
|
|
|
|
|
get: { session.voiceState.inputMode },
|
|
|
|
|
set: { session.setInputMode($0) }
|
|
|
|
|
)) {
|
|
|
|
|
Text("Voice Activation").tag(VoiceCatInputMode.voiceActivation)
|
|
|
|
|
Text("Push to Talk").tag(VoiceCatInputMode.pushToTalk)
|
|
|
|
|
Text("Always On").tag(VoiceCatInputMode.alwaysOn)
|
|
|
|
|
}
|
|
|
|
|
.accessibilityLabel("Voice input mode")
|
|
|
|
|
|
|
|
|
|
if session.voiceState.inputMode == .voiceActivation {
|
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
|
|
|
Text("VAD Threshold: \(String(format: "%.3f", session.voiceState.vadThreshold))")
|
|
|
|
|
.font(.caption)
|
|
|
|
|
Slider(
|
|
|
|
|
value: Binding(
|
|
|
|
|
get: { Double(session.voiceState.vadThreshold) },
|
|
|
|
|
set: { session.setVadThreshold(Float($0)) }
|
|
|
|
|
),
|
|
|
|
|
in: 0.001...0.1, step: 0.001
|
|
|
|
|
)
|
|
|
|
|
.accessibilityLabel("Voice activation threshold")
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(clients): persist input settings, add mic input gain, fix iOS chat + VoiceOver
Input mode (VAD/PTT/Always-On), VAD threshold, and the new mic gain were
applied to the core + UI but never saved, so every relaunch reset to VAD
defaults. Each client now persists them and re-applies on connect:
- iOS: UserDefaults (SessionState.loadAndApplyVoiceSettings + setter writes)
- macOS: UserDefaults via MainWindowController didSet + loadPersistedAudioSettings
(settings window also restores the VAD slider from the stored threshold)
- Windows: new Models/VoiceSettings.cs (JSON at %AppData%\VoiceCat\voice.json,
mirrors FeedbackSettings) loaded/applied in MainForm
Add global send-side mic gain API vc_set_input_gain (applied to MIC PCM in
on_capture_frame before the VAD gate, clamped to int16) + Swift/C# bindings,
and a 0-300% (default 100%) mic-volume slider on all three clients.
Fix iOS chat: ChatView called sendText(scope:.channel) with no targetId (0),
so channel messages went nowhere; now passes session.currentChannelId.
Fix iOS per-user tuning for VoiceOver: the tuning sheet was long-press
.contextMenu only (invisible to VoiceOver); UserRow now also exposes the same
buttons via .accessibilityActions (no visual change).
Verified: core builds clean; ctest 24/27 (3 pre-existing teardown crashes,
reproduced with changes stashed); VoiceCatMac + VoiceCatiOS (arm64 sim) build
SUCCEEDED; VoiceCat.Interop dotnet build succeeded. Windows App not built
(WinForms can't build on macOS) — follows existing patterns.
2026-06-23 03:35:26 +02:00
|
|
|
|
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
|
|
|
Text("Mic Volume: \(Int((session.voiceState.inputGain * 100).rounded()))%")
|
|
|
|
|
.font(.caption)
|
|
|
|
|
Slider(
|
|
|
|
|
value: Binding(
|
|
|
|
|
get: { Double(session.voiceState.inputGain) },
|
|
|
|
|
set: { session.setInputGain(Float($0)) }
|
|
|
|
|
),
|
2026-06-24 12:18:10 +02:00
|
|
|
in: 0...4, step: 0.05
|
feat(clients): persist input settings, add mic input gain, fix iOS chat + VoiceOver
Input mode (VAD/PTT/Always-On), VAD threshold, and the new mic gain were
applied to the core + UI but never saved, so every relaunch reset to VAD
defaults. Each client now persists them and re-applies on connect:
- iOS: UserDefaults (SessionState.loadAndApplyVoiceSettings + setter writes)
- macOS: UserDefaults via MainWindowController didSet + loadPersistedAudioSettings
(settings window also restores the VAD slider from the stored threshold)
- Windows: new Models/VoiceSettings.cs (JSON at %AppData%\VoiceCat\voice.json,
mirrors FeedbackSettings) loaded/applied in MainForm
Add global send-side mic gain API vc_set_input_gain (applied to MIC PCM in
on_capture_frame before the VAD gate, clamped to int16) + Swift/C# bindings,
and a 0-300% (default 100%) mic-volume slider on all three clients.
Fix iOS chat: ChatView called sendText(scope:.channel) with no targetId (0),
so channel messages went nowhere; now passes session.currentChannelId.
Fix iOS per-user tuning for VoiceOver: the tuning sheet was long-press
.contextMenu only (invisible to VoiceOver); UserRow now also exposes the same
buttons via .accessibilityActions (no visual change).
Verified: core builds clean; ctest 24/27 (3 pre-existing teardown crashes,
reproduced with changes stashed); VoiceCatMac + VoiceCatiOS (arm64 sim) build
SUCCEEDED; VoiceCat.Interop dotnet build succeeded. Windows App not built
(WinForms can't build on macOS) — follows existing patterns.
2026-06-23 03:35:26 +02:00
|
|
|
)
|
|
|
|
|
.accessibilityLabel("Microphone volume")
|
|
|
|
|
.accessibilityValue("\(Int((session.voiceState.inputGain * 100).rounded())) percent")
|
|
|
|
|
}
|
feat(clients): wire RNNoise mic noise reduction into Windows, macOS, and iOS
Expose the existing send-side vc_set_input_noise_reduction C ABI (MIC-only,
mono, LOCAL — denoises captured mic PCM before input gain and VAD/PTT gate)
as a persisted global toggle in each client's audio settings, applied live
and re-applied on Join Voice. Mirrors the existing mic-gain wiring pattern.
- Shared Swift (VoiceCatCore): add setInputNoiseReduction(_:) wrapper
- Windows: P/Invoke + SetInputNoiseReduction wrapper, MicNoiseReduction in
VoiceSettings, new checkbox in AudioSettingsForm (layout shifted +28px),
apply on Join Voice; also fix stale 'planned - currently passthrough'
label on the receive-side per-user NR checkbox (RNNoise now backs it)
- macOS: inputNoiseReduction state + UserDefaults in MainWindowController,
NR checkbox + nrChanged action in SettingsWindowController
- iOS: inputNoiseReduction in VoiceState + setter + restore in SessionState,
NR Toggle in SettingsView Voice section
Aux/screen are out of scope by design (core's NR guards kind == MIC). Apple
builds require a rebuilt VoiceCatCore.xcframework with VOICECAT_HAS_NS.
2026-06-23 14:11:18 +02:00
|
|
|
|
|
|
|
|
Toggle("Noise Reduction (RNNoise)", isOn: Binding(
|
|
|
|
|
get: { session.voiceState.inputNoiseReduction },
|
|
|
|
|
set: { session.setInputNoiseReduction($0) }
|
|
|
|
|
))
|
|
|
|
|
.accessibilityLabel("Microphone noise reduction")
|
|
|
|
|
.accessibilityHint("Denoises your microphone signal for everyone listening.")
|
2026-06-19 02:10:25 +02:00
|
|
|
}
|
|
|
|
|
|
feat(clients): event sound effects + optional text-to-speech
Add audible cues and optional spoken announcements for session events
(join/leave, channel + PM sent/recv, login, logout/connection-lost,
mic on/off, voice-activity, PTT) across all three clients, driven off
the shared C ABI vc_event stream so the mapping stays consistent.
TTS is off by default; when enabled it announces events and reads
message/PM bodies aloud. Master toggles + a sound-volume slider; the
per-utterance voice-activity and PTT cues default off. WAVs ship from
assets/sounds/.
Windows (built + verified): new VoiceCat.App/Notifications/ layer
(FeedbackSettings -> %AppData%\VoiceCat\feedback.json, SoundPlayerPool
via System.Media.SoundPlayer, SpeechAnnouncer via Prismatoid 0.3.0,
EventFeedback dispatcher); MainForm hooks; NotificationSettingsForm
under Settings > Notifications; csproj adds the Prismatoid PackageRef
and copies the WAVs into sounds\.
macOS + iOS (written, not yet built -- needs a Mac): shared
VoiceCatCore/Feedback/ (SoundEvent, EventFeedback = AVAudioPlayer pool
+ native AVSpeechSynthesizer, FeedbackSettings over UserDefaults); WAVs
bundled via Package.swift resources (.process). Hooks in SessionState/
AppState (iOS) and MainWindowController (macOS); settings UI in
SettingsView (iOS) and SettingsWindowController (macOS).
No core/server code touched; ctest --preset dev unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:20:11 +02:00
|
|
|
// MARK: - Notifications
|
|
|
|
|
Section("Notifications") {
|
|
|
|
|
Toggle("Event sounds", isOn: $soundsEnabled)
|
|
|
|
|
.accessibilityLabel("Play event sounds")
|
|
|
|
|
if soundsEnabled {
|
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
|
|
|
Text("Sound volume")
|
|
|
|
|
.font(.caption)
|
|
|
|
|
Slider(value: $soundsVolume, in: 0...1)
|
|
|
|
|
.accessibilityLabel("Sound volume")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Toggle("Speak events (text-to-speech)", isOn: $speechEnabled)
|
|
|
|
|
.accessibilityLabel("Speak events")
|
|
|
|
|
.accessibilityHint("Announces joins and leaves and reads message text aloud.")
|
|
|
|
|
Toggle("Your own voice-activity sounds", isOn: $selfTalkEnabled)
|
|
|
|
|
.accessibilityLabel("Voice activity sounds")
|
|
|
|
|
Toggle("Push-to-talk cue", isOn: $pttSoundEnabled)
|
|
|
|
|
.accessibilityLabel("Push to talk cue")
|
|
|
|
|
}
|
|
|
|
|
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
// MARK: - Admin
|
2026-06-19 02:10:25 +02:00
|
|
|
if session.permissions.canAdminAccounts || session.permissions.isAdmin {
|
|
|
|
|
Section("Administration") {
|
|
|
|
|
NavigationLink("Manage Accounts") {
|
|
|
|
|
AccountsView(session: session)
|
|
|
|
|
}
|
|
|
|
|
.accessibilityLabel("Manage server accounts")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
// MARK: - Server
|
2026-06-19 02:10:25 +02:00
|
|
|
Section("Server") {
|
|
|
|
|
Button(role: .destructive) {
|
|
|
|
|
session.stopMicStream()
|
|
|
|
|
appState.disconnect()
|
|
|
|
|
} label: {
|
|
|
|
|
Label("Disconnect", systemImage: "phone.down")
|
|
|
|
|
.foregroundStyle(.red)
|
|
|
|
|
}
|
|
|
|
|
.accessibilityLabel("Disconnect from server")
|
|
|
|
|
}
|
|
|
|
|
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
// MARK: - About
|
2026-06-19 02:10:25 +02:00
|
|
|
Section("About") {
|
|
|
|
|
Text(VoiceCatClient.versionString)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.accessibilityLabel("Version: \(VoiceCatClient.versionString)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.navigationTitle("Settings")
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
.onAppear {
|
|
|
|
|
router.refreshRoutes()
|
|
|
|
|
}
|
2026-06-19 02:10:25 +02:00
|
|
|
}
|
|
|
|
|
}
|
feat(ios): audio overhaul, Join/Leave Voice, channel-id sync fix, stereo mic capture
Three iOS client problems fixed plus a new core stereo-mic capture ABI:
1. Channel-id sync bug (mic button permanently dimmed): SessionState never
synced currentChannelId from the self user's channelId on connect, so the
mic button (gated on currentChannelId == 0) stayed dimmed. Added
syncSelfChannel() (mirrors macOS MainWindowController.swift:461,491,522);
called from init/.channelList/.userJoined/.userLeft/.userUpdated/.joinResult.
Added applyServerMuteState() + serverMuted/serverDeafened to VoiceState.
2. Join/Leave Voice button: replaced icon-only mic toggle with explicit
text button (parity with macOS). Mute/deafen disable when not in voice.
3. IOSAudioRouter.swift (new): full AVAudioSession routing layer — input
port selection, built-in mic orientation/polar patterns, Bluetooth
HFP/A2DP/Off modes, Standard/Raw mic processing, stereo capture, AirPlay,
UserDefaults persistence. AudioSessionManager delegates to it.
4. Core stereo-mic capture (append-only ABI): vc_set_capture_channels()
lets the core open the mic device in stereo (2-ch interleaved). LocalStream
gains capture_channels; ensure_audio_running reads it; audio_engine.cpp
capture_accum_ + on_capture updated to channel-aware accumulation. Test
test_stereo_mic_capture (headless, L!=R stereo round-trip). Swift wrapper
VoiceCatClient.setCaptureChannels.
5. Settings UI rework: AVAudioSession-derived input/output tree replaces
miniaudio device picker.
6. iOS deployment target raised to 18.0 (Package.swift + project.pbxproj).
swift-tools-version 6.0 with swiftLanguageModes .v5.
Docs: tech-stack.md, architecture.md, voice.md, roadmap.md, building.md
updated; stale 'vc_audio_suspend/resume deferred' claims corrected.
Verified: ctest --preset dev 21/21 green; swift test 6/6 green;
xcodebuild -target VoiceCatiOS -sdk iphonesimulator BUILD SUCCEEDED.
2026-06-19 13:17:52 +02:00
|
|
|
|
|
|
|
|
/// Human-readable label for AVAudioSession.PolarPattern raw values.
|
|
|
|
|
private func polarPatternLabel(_ rawValue: String) -> String {
|
|
|
|
|
switch rawValue {
|
|
|
|
|
case AVAudioSession.PolarPattern.omnidirectional.rawValue: return "Omnidirectional"
|
|
|
|
|
case AVAudioSession.PolarPattern.cardioid.rawValue: return "Cardioid"
|
|
|
|
|
case AVAudioSession.PolarPattern.subcardioid.rawValue: return "Subcardioid"
|
|
|
|
|
default: return rawValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// SwiftUI wrapper for AVRoutePickerView (AVKit's UIView for AirPlay route selection).
|
|
|
|
|
private struct RoutePickerButton: UIViewRepresentable {
|
|
|
|
|
func makeUIView(context: Context) -> AVRoutePickerView {
|
|
|
|
|
let view = AVRoutePickerView()
|
|
|
|
|
view.tintColor = .systemBlue
|
|
|
|
|
return view
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updateUIView(_ uiView: AVRoutePickerView, context: Context) {}
|
2026-06-19 02:10:25 +02:00
|
|
|
}
|