@agent-native/core 0.155.0 → 0.157.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +19 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +7 -2
- package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +3 -1
- package/corpus/templates/assets/app/global.css +4 -0
- package/corpus/templates/assets/app/i18n-data.ts +21 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +18 -15
- package/corpus/templates/brain/app/routes/sources.tsx +1 -2
- package/corpus/templates/calendar/app/hooks/use-bookings.ts +12 -2
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +64 -2
- package/corpus/templates/calendar/server/handlers/bookings.ts +219 -93
- package/corpus/templates/calendar/server/plugins/auth.ts +0 -2
- package/corpus/templates/clips/actions/create-folder.ts +7 -1
- package/corpus/templates/clips/actions/create-meeting.ts +6 -2
- package/corpus/templates/clips/actions/create-recording.ts +5 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +97 -65
- package/corpus/templates/clips/actions/import-loom-recording.ts +5 -3
- package/corpus/templates/clips/actions/request-recording-access.ts +272 -0
- package/corpus/templates/clips/actions/stitch-recordings.ts +5 -3
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +38 -63
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +177 -134
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +3 -21
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +13 -30
- package/corpus/templates/clips/app/i18n/en-US.ts +10 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +24 -17
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +151 -20
- package/corpus/templates/clips/changelog/2026-08-14-long-resumable-uploads-now-abort-cleanly-and-show-errors.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-14-standardized-share-popovers.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +11 -3
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +37 -8
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +3 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +8 -25
- package/corpus/templates/clips/desktop/src/app.tsx +24 -0
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +42 -13
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +11 -18
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +1 -0
- package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
- package/corpus/templates/clips/desktop/src/styles.css +24 -9
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +8 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +154 -176
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +115 -31
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +7 -0
- package/corpus/templates/clips/server/db/index.ts +1 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -1
- package/corpus/templates/clips/server/lib/emails.ts +23 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +55 -0
- package/corpus/templates/clips/server/lib/recordings.ts +2 -1
- package/corpus/templates/clips/server/lib/resumable-upload-cleanup.ts +49 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +34 -7
- package/corpus/templates/clips/server/lib/upload-interruption.ts +15 -1
- package/corpus/templates/clips/server/lib/upload-lease.ts +94 -9
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +82 -7
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +71 -12
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +4 -3
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +19 -0
- package/corpus/templates/clips/server/routes/api/uploads/reap.post.ts +8 -1
- package/corpus/templates/clips/shared/recording-link.ts +4 -0
- package/corpus/templates/clips/shared/share-loader-response.ts +2 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +7 -16
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +6 -2
- package/corpus/templates/factory/actions/view-screen.ts +15 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +58 -189
- package/corpus/templates/factory/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +7 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +3 -1
- package/corpus/templates/factory/app/routes/agents.tsx +9 -0
- package/corpus/templates/mail/server/plugins/auth.ts +0 -2
- package/corpus/templates/plan/app/components/plan/LocalCodebasePicker.tsx +50 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -19
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +98 -29
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +45 -91
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +94 -2
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +17 -29
- package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +7 -1
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +108 -19
- package/corpus/templates/slides/app/global.css +7 -40
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/lib/editor-shortcuts.ts +46 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +143 -45
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/production-agent.d.ts +30 -0
- package/dist/agent/production-agent.js +100 -8
- package/dist/agent/types.d.ts +9 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +22 -17
- package/dist/client/AssistantChat.d.ts +15 -1
- package/dist/client/AssistantChat.js +135 -48
- package/dist/client/HighlightedCodeBlock.js +12 -2
- package/dist/client/MultiTabAssistantChat.js +44 -27
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/chat/markdown-renderer.js +20 -5
- package/dist/client/chat/message-components.d.ts +2 -2
- package/dist/client/chat/message-components.js +69 -35
- package/dist/client/chat/run-recovery.d.ts +4 -1
- package/dist/client/chat/run-recovery.js +62 -30
- package/dist/client/chat/tool-call-display.d.ts +12 -3
- package/dist/client/chat/tool-call-display.js +79 -40
- package/dist/client/chat/widgets/DataChartRenderer.js +3 -1
- package/dist/client/chat/widgets/DataChartWidget.js +8 -2
- package/dist/client/chat/widgets/DataInsightsWidget.js +3 -1
- package/dist/client/chat/widgets/DataTableWidget.js +23 -9
- package/dist/client/chat/widgets/builtin-tool-renderers.js +3 -1
- package/dist/client/chat-first/apps-rail.js +1 -1
- package/dist/client/components/ui/message-scroller.d.ts +1 -1
- package/dist/client/components/ui/message-scroller.js +7 -3
- package/dist/client/composer/runtime-adapters.js +3 -2
- package/dist/client/context-xray/ContextMeter.js +5 -1
- package/dist/client/context-xray/ContextXRayPanel.js +5 -5
- package/dist/client/error-format.d.ts +4 -0
- package/dist/client/error-format.js +93 -5
- package/dist/client/i18n.js +83 -17
- package/dist/client/observability/ThumbsFeedback.js +10 -8
- package/dist/client/settings/AccountSettingsCard.js +48 -19
- package/dist/client/sharing/ShareButton.d.ts +1 -2
- package/dist/client/sharing/ShareButton.js +151 -81
- package/dist/client/sharing/ShareDialog.js +5 -4
- package/dist/client/use-chat-threads.js +10 -14
- package/dist/coding-tools/run-code.js +2 -1
- package/dist/coding-tools/sandbox/background.d.ts +1 -0
- package/dist/coding-tools/sandbox/background.js +8 -1
- package/dist/coding-tools/sandbox/executions-store.d.ts +2 -0
- package/dist/coding-tools/sandbox/executions-store.js +41 -3
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.d.ts +3 -0
- package/dist/localization/core-messages/ar-SA.js +541 -0
- package/dist/localization/core-messages/de-DE.d.ts +3 -0
- package/dist/localization/core-messages/de-DE.js +513 -0
- package/dist/localization/core-messages/en-US.d.ts +513 -0
- package/dist/localization/core-messages/en-US.js +513 -0
- package/dist/localization/core-messages/es-ES.d.ts +3 -0
- package/dist/localization/core-messages/es-ES.js +520 -0
- package/dist/localization/core-messages/fr-FR.d.ts +3 -0
- package/dist/localization/core-messages/fr-FR.js +520 -0
- package/dist/localization/core-messages/hi-IN.d.ts +3 -0
- package/dist/localization/core-messages/hi-IN.js +513 -0
- package/dist/localization/core-messages/ja-JP.d.ts +3 -0
- package/dist/localization/core-messages/ja-JP.js +506 -0
- package/dist/localization/core-messages/ko-KR.d.ts +3 -0
- package/dist/localization/core-messages/ko-KR.js +506 -0
- package/dist/localization/core-messages/pt-BR.d.ts +3 -0
- package/dist/localization/core-messages/pt-BR.js +520 -0
- package/dist/localization/core-messages/zh-CN.d.ts +3 -0
- package/dist/localization/core-messages/zh-CN.js +506 -0
- package/dist/localization/core-messages/zh-TW.d.ts +3 -0
- package/dist/localization/core-messages/zh-TW.js +506 -0
- package/dist/localization/core-messages.d.ts +524 -0
- package/dist/localization/core-messages.js +124 -0
- package/dist/localization/default-messages.d.ts +3 -0
- package/dist/localization/default-messages.js +3 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/plugin-options.d.ts +7 -2
- package/dist/server/agent-chat-plugin.d.ts +10 -0
- package/dist/server/agent-chat-plugin.js +144 -41
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-run-context.d.ts +7 -1
- package/dist/server/agent-run-context.js +9 -2
- package/dist/server/agent-teams-run-queue.d.ts +3 -0
- package/dist/server/agent-teams.js +38 -9
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +0 -2
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.js +18 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.js +2 -88
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +7 -0
- package/dist/server/request-context.js +15 -0
- package/dist/shared/markdown-block-split.d.ts +16 -0
- package/dist/shared/markdown-block-split.js +81 -5
- package/dist/styles/agent-native.css +13 -0
- package/dist/voice/voice-cleanup-prompt.js +1 -0
- package/docs/content/agent-surfaces.mdx +30 -0
- package/package.json +2 -2
- package/corpus/templates/clips/shared/mic-audio-cleanup.ts +0 -350
|
@@ -1206,6 +1206,7 @@ pub(crate) fn prepare_clip_sink(
|
|
|
1206
1206
|
height: u32,
|
|
1207
1207
|
include_mic: bool,
|
|
1208
1208
|
include_system_audio: bool,
|
|
1209
|
+
voice_cleanup_enabled: bool,
|
|
1209
1210
|
) -> Result<PreparedClipSink, String> {
|
|
1210
1211
|
let mut installed = slot.lock().map_err(|e| e.to_string())?;
|
|
1211
1212
|
if installed.is_some() {
|
|
@@ -1222,6 +1223,7 @@ pub(crate) fn prepare_clip_sink(
|
|
|
1222
1223
|
include_mic,
|
|
1223
1224
|
include_system_audio,
|
|
1224
1225
|
),
|
|
1226
|
+
voice_cleanup_enabled,
|
|
1225
1227
|
CustomWriterOutput::ClipHls,
|
|
1226
1228
|
None,
|
|
1227
1229
|
)?;
|
|
@@ -1374,6 +1376,7 @@ impl CustomScreenCaptureWriter {
|
|
|
1374
1376
|
capture_system_audio: bool,
|
|
1375
1377
|
include_audio: bool,
|
|
1376
1378
|
mix_live: bool,
|
|
1379
|
+
voice_cleanup_enabled: bool,
|
|
1377
1380
|
output: CustomWriterOutput,
|
|
1378
1381
|
audio_producer: Option<crate::capture_audio_bus::AudioProducer>,
|
|
1379
1382
|
) -> Result<Self, String> {
|
|
@@ -1524,6 +1527,7 @@ impl CustomScreenCaptureWriter {
|
|
|
1524
1527
|
segmented,
|
|
1525
1528
|
include_audio,
|
|
1526
1529
|
capture_system_audio,
|
|
1530
|
+
voice_cleanup_enabled,
|
|
1527
1531
|
)?),
|
|
1528
1532
|
)
|
|
1529
1533
|
} else {
|
|
@@ -2182,18 +2186,6 @@ const MIC_AGC_BOOST_SECONDS: f32 = 1.5;
|
|
|
2182
2186
|
const MIC_AGC_WARMUP_SECONDS: f32 = 0.2;
|
|
2183
2187
|
/// How close to the target counts as levelled and ends the warmup.
|
|
2184
2188
|
const MIC_AGC_CONVERGED_TOLERANCE: f32 = 0.05;
|
|
2185
|
-
/// Conservative voice cleanup for the realtime microphone path. The browser
|
|
2186
|
-
/// recorder gets a comparable Web Audio chain; native live uploads cannot
|
|
2187
|
-
/// wait for the later FFmpeg normalization pass, so this stays causal and
|
|
2188
|
-
/// source-local here.
|
|
2189
|
-
const MIC_CLEANUP_HIGH_PASS_HZ: f32 = 65.0;
|
|
2190
|
-
const MIC_CLEANUP_NOTCH_FREQUENCIES_HZ: [f32; 4] = [50.0, 60.0, 100.0, 120.0];
|
|
2191
|
-
const MIC_CLEANUP_NOTCH_Q: f32 = 18.0;
|
|
2192
|
-
const MIC_EXPANDER_THRESHOLD_DB: f32 = -48.0;
|
|
2193
|
-
const MIC_EXPANDER_RATIO: f32 = 2.0;
|
|
2194
|
-
const MIC_EXPANDER_MAX_REDUCTION_DB: f32 = 12.0;
|
|
2195
|
-
const MIC_EXPANDER_ATTACK_SECONDS: f32 = 0.012;
|
|
2196
|
-
const MIC_EXPANDER_RELEASE_SECONDS: f32 = 0.18;
|
|
2197
2189
|
/// -1.0 dBFS, near the true-peak ceiling the offline loudnorm chain targets.
|
|
2198
2190
|
const MIX_LIMIT_CEILING: f32 = 0.891;
|
|
2199
2191
|
const MIX_LIMIT_RELEASE_SECONDS: f32 = 0.15;
|
|
@@ -2275,153 +2267,99 @@ impl MicAutoGain {
|
|
|
2275
2267
|
}
|
|
2276
2268
|
}
|
|
2277
2269
|
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
y1: f32,
|
|
2288
|
-
y2: f32,
|
|
2270
|
+
/// RNNoise-derived denoising for the native live-upload path. ScreenCaptureKit
|
|
2271
|
+
/// hands us stereo f32 PCM, while `nnnoiseless` consumes mono 16-bit-scaled
|
|
2272
|
+
/// f32 frames at 48 kHz. Keep the frame buffering here, before the source
|
|
2273
|
+
/// timeline, so the denoiser never sees zero-filled timeline gaps as speech.
|
|
2274
|
+
struct MicDenoiser {
|
|
2275
|
+
state: Box<nnnoiseless::DenoiseState<'static>>,
|
|
2276
|
+
pending: Vec<f32>,
|
|
2277
|
+
pending_start_pts: Option<f64>,
|
|
2278
|
+
first_frame: bool,
|
|
2289
2279
|
}
|
|
2290
2280
|
|
|
2291
|
-
impl
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
b0: b0 / a0,
|
|
2295
|
-
b1: b1 / a0,
|
|
2296
|
-
b2: b2 / a0,
|
|
2297
|
-
a1: a1 / a0,
|
|
2298
|
-
a2: a2 / a0,
|
|
2299
|
-
x1: 0.0,
|
|
2300
|
-
x2: 0.0,
|
|
2301
|
-
y1: 0.0,
|
|
2302
|
-
y2: 0.0,
|
|
2303
|
-
}
|
|
2304
|
-
}
|
|
2305
|
-
|
|
2306
|
-
fn high_pass(frequency_hz: f32, q: f32, sample_rate: f32) -> Self {
|
|
2307
|
-
let omega = 2.0 * std::f32::consts::PI * frequency_hz / sample_rate;
|
|
2308
|
-
let cosine = omega.cos();
|
|
2309
|
-
let sine = omega.sin();
|
|
2310
|
-
let alpha = sine / (2.0 * q);
|
|
2311
|
-
Self::from_coefficients(
|
|
2312
|
-
(1.0 + cosine) / 2.0,
|
|
2313
|
-
-(1.0 + cosine),
|
|
2314
|
-
(1.0 + cosine) / 2.0,
|
|
2315
|
-
1.0 + alpha,
|
|
2316
|
-
-2.0 * cosine,
|
|
2317
|
-
1.0 - alpha,
|
|
2318
|
-
)
|
|
2319
|
-
}
|
|
2281
|
+
impl MicDenoiser {
|
|
2282
|
+
const SAMPLE_RATE: f64 = 48_000.0;
|
|
2283
|
+
const SCALE: f32 = 32_768.0;
|
|
2320
2284
|
|
|
2321
|
-
fn
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
-2.0 * cosine,
|
|
2329
|
-
1.0,
|
|
2330
|
-
1.0 + alpha,
|
|
2331
|
-
-2.0 * cosine,
|
|
2332
|
-
1.0 - alpha,
|
|
2333
|
-
)
|
|
2285
|
+
fn new() -> Self {
|
|
2286
|
+
Self {
|
|
2287
|
+
state: nnnoiseless::DenoiseState::new(),
|
|
2288
|
+
pending: Vec::new(),
|
|
2289
|
+
pending_start_pts: None,
|
|
2290
|
+
first_frame: true,
|
|
2291
|
+
}
|
|
2334
2292
|
}
|
|
2335
2293
|
|
|
2336
|
-
fn
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
self.
|
|
2341
|
-
self.x1 = input;
|
|
2342
|
-
self.y2 = self.y1;
|
|
2343
|
-
self.y1 = output;
|
|
2344
|
-
output
|
|
2294
|
+
fn reset(&mut self) {
|
|
2295
|
+
self.state = nnnoiseless::DenoiseState::new();
|
|
2296
|
+
self.pending.clear();
|
|
2297
|
+
self.pending_start_pts = None;
|
|
2298
|
+
self.first_frame = true;
|
|
2345
2299
|
}
|
|
2346
|
-
}
|
|
2347
2300
|
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2301
|
+
fn process(&mut self, interleaved: &[f32], pts_seconds: f64) -> Vec<(Vec<f32>, f64)> {
|
|
2302
|
+
let frame_count = interleaved.len() / 2;
|
|
2303
|
+
if frame_count == 0 {
|
|
2304
|
+
return Vec::new();
|
|
2305
|
+
}
|
|
2352
2306
|
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
Biquad::notch(
|
|
2359
|
-
MIC_CLEANUP_NOTCH_FREQUENCIES_HZ[index],
|
|
2360
|
-
MIC_CLEANUP_NOTCH_Q,
|
|
2361
|
-
sample_rate,
|
|
2362
|
-
)
|
|
2363
|
-
}),
|
|
2307
|
+
if let Some(start) = self.pending_start_pts {
|
|
2308
|
+
let expected = start + self.pending.len() as f64 / Self::SAMPLE_RATE;
|
|
2309
|
+
if (pts_seconds - expected).abs() > 0.02 {
|
|
2310
|
+
self.reset();
|
|
2311
|
+
}
|
|
2364
2312
|
}
|
|
2365
|
-
|
|
2313
|
+
if self.pending_start_pts.is_none() {
|
|
2314
|
+
self.pending_start_pts = Some(pts_seconds);
|
|
2315
|
+
}
|
|
2316
|
+
self.pending
|
|
2317
|
+
.extend(interleaved[..frame_count * 2].chunks_exact(2).map(|frame| {
|
|
2318
|
+
((frame[0] + frame[1]) * 0.5 * Self::SCALE).clamp(-Self::SCALE, Self::SCALE)
|
|
2319
|
+
}));
|
|
2366
2320
|
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2321
|
+
let frame_size = nnnoiseless::DenoiseState::FRAME_SIZE;
|
|
2322
|
+
let mut output = Vec::new();
|
|
2323
|
+
while self.pending.len() >= frame_size {
|
|
2324
|
+
let start = self.pending_start_pts.unwrap_or(pts_seconds);
|
|
2325
|
+
let input = self.pending[..frame_size].to_vec();
|
|
2326
|
+
let mut denoised = vec![0.0_f32; frame_size];
|
|
2327
|
+
self.state.process_frame(&mut denoised, &input);
|
|
2328
|
+
let samples = if self.first_frame { &input } else { &denoised };
|
|
2329
|
+
output.push((Self::stereo_samples(samples), start));
|
|
2330
|
+
self.first_frame = false;
|
|
2331
|
+
self.pending.drain(..frame_size);
|
|
2332
|
+
self.pending_start_pts = Some(start + frame_size as f64 / Self::SAMPLE_RATE);
|
|
2371
2333
|
}
|
|
2372
|
-
|
|
2334
|
+
output
|
|
2373
2335
|
}
|
|
2374
|
-
}
|
|
2375
2336
|
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
left: MicCleanupChannel::new(sample_rate),
|
|
2394
|
-
right: MicCleanupChannel::new(sample_rate),
|
|
2395
|
-
envelope: 0.0,
|
|
2396
|
-
gain: 1.0,
|
|
2397
|
-
envelope_coefficient: one_pole_coefficient(0.015, sample_rate as i32),
|
|
2398
|
-
attack_coefficient: one_pole_coefficient(
|
|
2399
|
-
MIC_EXPANDER_ATTACK_SECONDS,
|
|
2400
|
-
sample_rate as i32,
|
|
2401
|
-
),
|
|
2402
|
-
release_coefficient: one_pole_coefficient(
|
|
2403
|
-
MIC_EXPANDER_RELEASE_SECONDS,
|
|
2404
|
-
sample_rate as i32,
|
|
2405
|
-
),
|
|
2406
|
-
}
|
|
2337
|
+
fn flush(&mut self) -> Vec<(Vec<f32>, f64)> {
|
|
2338
|
+
if self.pending.is_empty() {
|
|
2339
|
+
return Vec::new();
|
|
2340
|
+
}
|
|
2341
|
+
let frame_size = nnnoiseless::DenoiseState::FRAME_SIZE;
|
|
2342
|
+
let count = self.pending.len();
|
|
2343
|
+
let start = self.pending_start_pts.unwrap_or(0.0);
|
|
2344
|
+
let mut input = vec![0.0_f32; frame_size];
|
|
2345
|
+
input[..count].copy_from_slice(&self.pending);
|
|
2346
|
+
let mut denoised = vec![0.0_f32; frame_size];
|
|
2347
|
+
self.state.process_frame(&mut denoised, &input);
|
|
2348
|
+
let samples = if self.first_frame { &input } else { &denoised };
|
|
2349
|
+
let result = vec![(Self::stereo_samples(&samples[..count]), start)];
|
|
2350
|
+
self.pending.clear();
|
|
2351
|
+
self.pending_start_pts = None;
|
|
2352
|
+
self.first_frame = false;
|
|
2353
|
+
result
|
|
2407
2354
|
}
|
|
2408
2355
|
|
|
2409
|
-
fn
|
|
2410
|
-
let
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
(below_threshold * (1.0 - 1.0 / MIC_EXPANDER_RATIO)).min(MIC_EXPANDER_MAX_REDUCTION_DB);
|
|
2417
|
-
let target_gain = 10.0_f32.powf(-reduction_db / 20.0);
|
|
2418
|
-
let coefficient = if target_gain > self.gain {
|
|
2419
|
-
self.attack_coefficient
|
|
2420
|
-
} else {
|
|
2421
|
-
self.release_coefficient
|
|
2422
|
-
};
|
|
2423
|
-
self.gain += (target_gain - self.gain) * coefficient;
|
|
2424
|
-
(filtered.0 * self.gain, filtered.1 * self.gain)
|
|
2356
|
+
fn stereo_samples(mono: &[f32]) -> Vec<f32> {
|
|
2357
|
+
let mut stereo = Vec::with_capacity(mono.len() * 2);
|
|
2358
|
+
for &sample in mono {
|
|
2359
|
+
let sample = (sample / Self::SCALE).clamp(-1.0, 1.0);
|
|
2360
|
+
stereo.extend([sample, sample]);
|
|
2361
|
+
}
|
|
2362
|
+
stereo
|
|
2425
2363
|
}
|
|
2426
2364
|
}
|
|
2427
2365
|
|
|
@@ -2458,15 +2396,15 @@ impl PeakLimiter {
|
|
|
2458
2396
|
/// is reachable without constructing a `CMFormatDescription` — the reported bug
|
|
2459
2397
|
/// was a wrong per-frame weight, so that arithmetic is what needs covering.
|
|
2460
2398
|
struct MixGainStage {
|
|
2461
|
-
|
|
2399
|
+
voice_cleanup_enabled: bool,
|
|
2462
2400
|
mic: MicAutoGain,
|
|
2463
2401
|
limiter: PeakLimiter,
|
|
2464
2402
|
}
|
|
2465
2403
|
|
|
2466
2404
|
impl MixGainStage {
|
|
2467
|
-
fn new(sample_rate: i32) -> Self {
|
|
2405
|
+
fn new(sample_rate: i32, voice_cleanup_enabled: bool) -> Self {
|
|
2468
2406
|
Self {
|
|
2469
|
-
|
|
2407
|
+
voice_cleanup_enabled,
|
|
2470
2408
|
mic: MicAutoGain::new(sample_rate),
|
|
2471
2409
|
limiter: PeakLimiter::new(sample_rate),
|
|
2472
2410
|
}
|
|
@@ -2476,8 +2414,11 @@ impl MixGainStage {
|
|
|
2476
2414
|
/// at unity and the mic is only ever boosted, with the limiter owning peak
|
|
2477
2415
|
/// safety for the sum.
|
|
2478
2416
|
fn frame(&mut self, system: (f32, f32), mic: (f32, f32)) -> (f32, f32) {
|
|
2479
|
-
let
|
|
2480
|
-
|
|
2417
|
+
let mic_gain = if self.voice_cleanup_enabled {
|
|
2418
|
+
self.mic.next_gain(mic.0, mic.1)
|
|
2419
|
+
} else {
|
|
2420
|
+
1.0
|
|
2421
|
+
};
|
|
2481
2422
|
let left = system.0 + mic.0 * mic_gain;
|
|
2482
2423
|
let right = system.1 + mic.1 * mic_gain;
|
|
2483
2424
|
let limit = self.limiter.next_gain(left.abs().max(right.abs()));
|
|
@@ -2601,6 +2542,7 @@ struct LiveAudioMixer {
|
|
|
2601
2542
|
out_pos: i64,
|
|
2602
2543
|
system: MixerTimeline,
|
|
2603
2544
|
mic: MixerTimeline,
|
|
2545
|
+
mic_denoiser: Option<MicDenoiser>,
|
|
2604
2546
|
/// Gain stage, carried across chunks: emitted frames are already uploaded,
|
|
2605
2547
|
/// so its state is the only memory the mix has.
|
|
2606
2548
|
gain: MixGainStage,
|
|
@@ -2612,6 +2554,7 @@ impl LiveAudioMixer {
|
|
|
2612
2554
|
rebase_output: bool,
|
|
2613
2555
|
include_microphone: bool,
|
|
2614
2556
|
capture_system_audio: bool,
|
|
2557
|
+
voice_cleanup_enabled: bool,
|
|
2615
2558
|
) -> Result<Self, String> {
|
|
2616
2559
|
let sample_rate = AUDIO_OUTPUT_SAMPLE_RATE as i32;
|
|
2617
2560
|
let asbd = AudioStreamBasicDescription {
|
|
@@ -2657,7 +2600,8 @@ impl LiveAudioMixer {
|
|
|
2657
2600
|
out_pos: 0,
|
|
2658
2601
|
system: MixerTimeline::new(),
|
|
2659
2602
|
mic: MixerTimeline::new(),
|
|
2660
|
-
|
|
2603
|
+
mic_denoiser: voice_cleanup_enabled.then(MicDenoiser::new),
|
|
2604
|
+
gain: MixGainStage::new(sample_rate, voice_cleanup_enabled),
|
|
2661
2605
|
created_at: Instant::now(),
|
|
2662
2606
|
})
|
|
2663
2607
|
}
|
|
@@ -2666,6 +2610,22 @@ impl LiveAudioMixer {
|
|
|
2666
2610
|
/// position, zero-filling any gap since the previous push (capped so a
|
|
2667
2611
|
/// glitched timestamp can't allocate gigabytes).
|
|
2668
2612
|
fn push(&mut self, source: MixSource, interleaved: &[f32], pts_seconds: f64) {
|
|
2613
|
+
if source == MixSource::Mic {
|
|
2614
|
+
let processed = self
|
|
2615
|
+
.mic_denoiser
|
|
2616
|
+
.as_mut()
|
|
2617
|
+
.map(|denoiser| denoiser.process(interleaved, pts_seconds));
|
|
2618
|
+
if let Some(processed) = processed {
|
|
2619
|
+
for (processed, processed_pts) in processed {
|
|
2620
|
+
self.push_timeline(MixSource::Mic, &processed, processed_pts);
|
|
2621
|
+
}
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
self.push_timeline(source, interleaved, pts_seconds);
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
fn push_timeline(&mut self, source: MixSource, interleaved: &[f32], pts_seconds: f64) {
|
|
2669
2629
|
let frames = interleaved.len() / 2;
|
|
2670
2630
|
if frames == 0 {
|
|
2671
2631
|
return;
|
|
@@ -2694,6 +2654,11 @@ impl LiveAudioMixer {
|
|
|
2694
2654
|
/// Update the accumulated pause offset (seconds) applied to incoming
|
|
2695
2655
|
/// source PTS in `push`. Set from the writer's shared offset on resume.
|
|
2696
2656
|
fn set_pause_offset(&mut self, seconds: f64) {
|
|
2657
|
+
if seconds > self.pause_offset_seconds + f64::EPSILON {
|
|
2658
|
+
if let Some(denoiser) = self.mic_denoiser.as_mut() {
|
|
2659
|
+
denoiser.reset();
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2697
2662
|
self.pause_offset_seconds = seconds;
|
|
2698
2663
|
}
|
|
2699
2664
|
|
|
@@ -2799,6 +2764,14 @@ impl LiveAudioMixer {
|
|
|
2799
2764
|
&mut self,
|
|
2800
2765
|
flush: bool,
|
|
2801
2766
|
) -> Result<Vec<screencapturekit::cm::CMSampleBuffer>, String> {
|
|
2767
|
+
if flush {
|
|
2768
|
+
let processed = self.mic_denoiser.as_mut().map(MicDenoiser::flush);
|
|
2769
|
+
if let Some(processed) = processed {
|
|
2770
|
+
for (processed, processed_pts) in processed {
|
|
2771
|
+
self.push_timeline(MixSource::Mic, &processed, processed_pts);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2802
2775
|
if self.anchor_seconds.is_none() {
|
|
2803
2776
|
return Ok(Vec::new());
|
|
2804
2777
|
}
|
|
@@ -4117,6 +4090,7 @@ pub(crate) fn start_custom_screencapturekit_backend_at(
|
|
|
4117
4090
|
CustomWriterOutput::Standard
|
|
4118
4091
|
};
|
|
4119
4092
|
let mix_live = live_audio_mixing_enabled(output, include_audio, capture_system_audio);
|
|
4093
|
+
let voice_cleanup_enabled = crate::config::feature_config(app).voice_cleanup_enabled;
|
|
4120
4094
|
// The active custom capture is the single physical owner of mic/system
|
|
4121
4095
|
// audio for both Rewind and ordinary Clips. Live transcription subscribes
|
|
4122
4096
|
// to this producer instead of opening a competing SCK/AVAudioEngine input,
|
|
@@ -4135,6 +4109,7 @@ pub(crate) fn start_custom_screencapturekit_backend_at(
|
|
|
4135
4109
|
capture_system_audio,
|
|
4136
4110
|
include_audio,
|
|
4137
4111
|
mix_live,
|
|
4112
|
+
voice_cleanup_enabled,
|
|
4138
4113
|
output,
|
|
4139
4114
|
audio_producer,
|
|
4140
4115
|
)?;
|
|
@@ -4343,36 +4318,39 @@ mod fragment_fence_tests {
|
|
|
4343
4318
|
}
|
|
4344
4319
|
|
|
4345
4320
|
#[test]
|
|
4346
|
-
fn
|
|
4347
|
-
let
|
|
4348
|
-
let
|
|
4349
|
-
let
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
);
|
|
4321
|
+
fn mic_denoiser_preserves_frame_boundaries_and_duration() {
|
|
4322
|
+
let mut denoiser = MicDenoiser::new();
|
|
4323
|
+
let first = vec![0.08_f32; 300 * 2];
|
|
4324
|
+
let second = vec![0.12_f32; 300 * 2];
|
|
4325
|
+
|
|
4326
|
+
assert!(denoiser.process(&first, 10.0).is_empty());
|
|
4327
|
+
let processed = denoiser.process(&second, 10.0 + 300.0 / 48_000.0);
|
|
4328
|
+
assert_eq!(processed.len(), 1);
|
|
4329
|
+
assert_eq!(processed[0].0.len(), 480 * 2);
|
|
4330
|
+
assert_eq!(processed[0].1, 10.0);
|
|
4331
|
+
assert!(processed[0].0.iter().all(|sample| sample.is_finite()));
|
|
4332
|
+
assert!(processed[0].0.iter().any(|sample| sample.abs() > 0.01));
|
|
4333
|
+
|
|
4334
|
+
let flushed = denoiser.flush();
|
|
4335
|
+
assert_eq!(flushed.len(), 1);
|
|
4336
|
+
assert_eq!(flushed[0].0.len(), 120 * 2);
|
|
4337
|
+
assert!(flushed[0].0.iter().all(|sample| sample.is_finite()));
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
#[test]
|
|
4341
|
+
fn mic_denoiser_resets_pending_audio_after_pause() {
|
|
4342
|
+
let mut denoiser = MicDenoiser::new();
|
|
4343
|
+
let input = vec![0.1_f32; 120 * 2];
|
|
4344
|
+
assert!(denoiser.process(&input, 10.0).is_empty());
|
|
4345
|
+
denoiser.reset();
|
|
4346
|
+
assert!(denoiser.flush().is_empty());
|
|
4347
|
+
assert!(denoiser.process(&input, 20.0).is_empty());
|
|
4348
|
+
assert_eq!(denoiser.flush()[0].1, 20.0);
|
|
4371
4349
|
}
|
|
4372
4350
|
|
|
4373
4351
|
#[test]
|
|
4374
4352
|
fn mic_only_mixer_does_not_wait_for_a_disabled_system_source() {
|
|
4375
|
-
let mixer = LiveAudioMixer::new(false, true, false).unwrap();
|
|
4353
|
+
let mixer = LiveAudioMixer::new(false, true, false, true).unwrap();
|
|
4376
4354
|
assert_eq!(mixer.compute_safe_end(false), 0);
|
|
4377
4355
|
}
|
|
4378
4356
|
|