Skip to content

feat: lower deployment target to macOS 15 with macOS 26 API fallback - #17

Open
matyle wants to merge 2 commits into
franklioxygen:mainfrom
matyle:feat/lower-deployment-target
Open

feat: lower deployment target to macOS 15 with macOS 26 API fallback#17
matyle wants to merge 2 commits into
franklioxygen:mainfrom
matyle:feat/lower-deployment-target

Conversation

@matyle

@matyle matyle commented Jun 10, 2026

Copy link
Copy Markdown
  • Change deployment target from macOS 26 to macOS 15 (Sequoia)
  • Add SUPPORTS_SPEECH_TRANSCRIBER compile-time flag for macOS 26 APIs
  • Wrap SpeechTranscriber, SpeechAnalyzer, AnalyzerInput in conditional compilation blocks with @available(macOS 26.0, *) runtime guards
  • Use SFSpeechRecognizer as fallback for speech recognition on macOS 15
  • Update README with tiered system requirements

- Change deployment target from macOS 26 to macOS 15 (Sequoia)
- Add SUPPORTS_SPEECH_TRANSCRIBER compile-time flag for macOS 26 APIs
- Wrap SpeechTranscriber, SpeechAnalyzer, AnalyzerInput in conditional
  compilation blocks with @available(macOS 26.0, *) runtime guards
- Use SFSpeechRecognizer as fallback for speech recognition on macOS 15
- Update README with tiered system requirements
@codacy-production

codacy-production Bot commented Jun 10, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 32 complexity · 8 duplication

Metric Results
Complexity 32
Duplication 8

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

- Add 'System Audio' option to capture all system audio (not just single app)
- Reduce silence commit timers for faster subtitle response:
  - ASR inactivity: 600-690ms → 400-530ms
  - VAD silence: 280ms → 150ms
- Reduce ModeConfig thresholds for Follow mode
- Change default subtitle mode from Balanced to Follow
- Add AnyAudioCapture protocol for unified audio capture interface

@franklioxygen franklioxygen left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the deployment-target lowering + System Audio / timing changes. The macOS 15 fallback work (commit 1) looks solid. The main blocker is that SystemAudioCapture (commit 2) never installs a process tap, so it will capture silence. Detailed inline comments below. Consider splitting commit 2 (System Audio + timing retune) into its own PR.

}

let outputUID = try outputDevice.uid
let aggregateDescription: [String: Any] = [

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical: this will capture silence. This aggregate device is built from the default output device but never creates a process tap and never sets kAudioAggregateDeviceTapListKey. Compare to ApplicationAudioCapture above, which creates a CATapDescription via system.makeProcessTap(...) and wires it into the aggregate's tap list — that's what actually routes playback audio to the IOProc. Without a tap, the aggregate input stream has no playback signal, so handleCapturedAudio receives empty/silent buffers. For system-wide capture, use a global tap (e.g. CATapDescription(stereoGlobalTapButExcludeProcesses:)) and add it to kAudioAggregateDeviceTapListKey.

self.aggregateDevice = aggregateDevice

// Use a 16-bit stereo format as fallback (44.1kHz, 2ch)
guard let tapFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 44100, channels: 2, interleaved: false) else {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded tap format. Sample rate / channels / format are hardcoded (44100 / 2ch / float32) instead of querying the real device/tap StreamBasicDescription the way ApplicationAudioCapture does. If the device actually runs at 48 kHz, the downstream converter interprets the data at the wrong rate → wrong pitch/speed → garbage ASR. Also the comment above says 16-bit stereo but the format is pcmFormatFloat32 — the comment is wrong.

tapFormat = nil
}

private func handleCapturedAudio(_ ioData: UnsafeMutableAudioBufferListPointer) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fragile channel/buffer handling. This only copies ioData.first into floatChannelData[0] of a buffer declared as 2-channel, leaving channel 1 uninitialized, and it assumes a non-interleaved single-buffer layout. If the device delivers interleaved or multi-buffer audio, this produces noise or reads only half the frames. Derive channel count and layout from the actual stream format rather than assuming.

) {
self.queue = queue
self.audioHandler = audioHandler
self.errorHandler = errorHandler

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errorHandler is stored but never invoked anywhere in SystemAudioCapture — all failures are thrown from start(). Either wire it up for async IOProc-time errors or drop it.

Comment thread Package.swift
.copy("Resources/silero_vad.onnx"),
],
swiftSettings: [
.define("SUPPORTS_SPEECH_TRANSCRIBER"),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUPPORTS_SPEECH_TRANSCRIBER is defined unconditionally, so the #else (macOS 15 SDK) branches are dead code in the default build and never compiled or tested — you still need the macOS 26 SDK to compile; the deployment-target lowering only helps at runtime. That's a fine design (the flag is meant to be toggled off when building on an older toolchain), but please add a comment here explaining when to disable it, since no CI build exercises the #if !SUPPORTS_SPEECH_TRANSCRIBER path.

Comment thread Package.swift
@@ -1,4 +1,4 @@
// swift-tools-version: 5.10
// swift-tools-version: 5.9

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swift-tools-version downgrade 5.10 → 5.9 is unexplained — presumably to support an older Xcode. Worth confirming this is intentional and noting why.

case .microphone:
return AppLocalization.string(.microphone, languageID: languageID)
case .systemAudio:
return "System Audio"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded English string bypasses AppLocalization — every other source uses AppLocalization.string(...). Same for InputSource.systemAudio's name/detail below. The zh-CN UI will show English here.

minSilenceCommitMs: 200
)

static let follow = ModeConfig(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These timing retunes (plus the default mode change balanced → follow in AppSettings) are a behavior change for all users, unrelated to the deployment-target goal of this PR. Recommend splitting into a separate PR with rationale/validation — the commit message asserts the new numbers but there's no evidence they were tested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants