noise.
On-device video editor
You work on top of the raw clip: the original is never touched and the clean video is only born on export.
Context
noise is a native iOS/iPadOS video editor that automatically cleans up talking videos (reels, vlogs, tutorials, talking-heads). Instead of cutting by hand, you switch on a set of «correctors» that detect and remove what's redundant in the audio —silences, long pauses, fillers, repetitions and stutters— and it also generates automatic subtitles burned into the video.
It's a tool to speed up the first cleanup pass —the most mechanical and tedious one— before the footage goes to final editing. It doesn't replace your editing suite: it takes the boring work off it.
All processing happens on-device: the audio never leaves your phone. Privacy as a feature, not a promise.
The challenge
The biggest architectural challenge: five different correctors must combine into ONE result that feeds both the preview (AVPlayer) and the export (AVAssetExportSession). If preview and export diverge, «what you see» stops being «what you save». On top of that: detecting silences without libraries (reading PCM by hand), transcribing on-device with the new iOS 26 API (no 1-minute limit like the old SFSpeechRecognizer) and mapping two timelines —source and composition— because cuts reorder the time of everything anchored to it.
The solution
A single source of truth for cuts: every corrector contributes the ranges it wants removed and one method (computeCuts) merges them and derives the complement → the ranges to keep. That same list feeds both preview and exporter. Silence detection runs on energy (RMS→dB over 20 ms windows) with no ML or third parties, and «long pauses» reuses the same detector with different parameters: an entire corrector for free.
/// Único sitio donde se decide qué se corta. Cada corrector activo aporta
/// tramos; se unen y se deriva el complemento (lo que se conserva).
/// Preview y export consumen exactamente la misma lista → WYSIWYG.
private func computeCuts() async {
var cuts: [ClosedRange<Double>] = []
if removeSilences { cuts += await silenceCuts(url: sourceURL) }
if removeLongPauses { cuts += await longPauseCuts(url: sourceURL) }
if removeFillers { cuts += fillerCuts() }
if removeRepetitions { cuts += repetitionCuts() }
removedSegments = Self.merge(cuts) // une solapados
keepRanges = Self.complement(of: removedSegments, // lo que se conserva
within: 0...duration)
}
// El mismo keepRanges viaja a preview y export como CMTimeRange:
private var keepCMRanges: [CMTimeRange]? {
keepRanges.map { CMTimeRange(
start: CMTime(seconds: $0.lowerBound, preferredTimescale: 600),
end: CMTime(seconds: $0.upperBound, preferredTimescale: 600)) }
}The preview IS the export: the same AVMutableComposition feeds what you see and what you save. Zero surprises on export.
The app






Architecture & stack
100% native, zero third-party dependencies. SwiftUI for all UI; AVFoundation for PCM reading, composition and export; Speech (iOS 26) with SpeechAnalyzer for on-device transcription with per-word timing; FoundationModels (Apple Intelligence) on-device for optional semantic repetition detection; Core Animation to burn animated subtitles. Lightweight JSON persistence: a project is a reference to the Photos video plus its settings —the video is never copied.
// Nivel de energía de una ventana de muestras PCM (20 ms) en decibelios.
// El mismo cálculo sirve para "pausas largas" cambiando los parámetros.
func rmsDB(_ s: ArraySlice<Int16>) -> Float {
guard !s.isEmpty else { return -120 }
var sum = 0.0
for v in s { let f = Double(v) / 32768.0; sum += f * f }
let rms = (sum / Double(s.count)).squareRoot()
return rms > 0 ? Float(20 * log10(rms)) : -120
}Five combinable correctors in a single cut pipeline; adding a new one is just one more branch. The architecture is built to grow.
Apple Intelligence, on-device
Repetitions are detected in two layers. A deterministic layer (n-gram) guarantees a quality floor on every device; on top, an optional, gated Apple Intelligence pass catches semantic redundancies the n-gram misses («I want to go… actually let's go»). The on-device model returns the indices to remove via structured output (guided generation), and never cuts on its own: every suggestion lands in a reviewable list.
import FoundationModels
// Capa de IA OPCIONAL y gated: solo corre si el dispositivo la soporta.
static var isAvailable: Bool {
if case .available = SystemLanguageModel.default.availability { return true }
return false
}
// Salida estructurada que el modelo está OBLIGADO a producir:
@Generable
struct RedundantSpanList {
@Guide(description: "Tramos redundantes a eliminar; vacío si no hay ninguno")
var spans: [RedundantSpan]
}
@Generable
struct RedundantSpan {
@Guide(description: "Índice de la primera palabra a eliminar, inclusive")
var start: Int
@Guide(description: "Índice de la última palabra a eliminar, inclusive")
var end: Int
}Deliverables
- ✓Silence corrector (configurable RMS→dB)
- ✓Long-pause corrector (caps to a maximum)
- ✓Filler corrector (es/en lexicon, reviewable list)
- ✓Repetition corrector (n-gram + optional Apple Intelligence)
- ✓On-device automatic subtitles (per-word timing)
- ✓6 subtitle animations (karaoke, single word, pop…)
- ✓WYSIWYG preview (preview = export)
- ✓Non-destructive editing (Photos reference)
How it grew
- Kickoff + engine port
The editing engine (silences + export) was ported from an earlier project, ideas365.
- Unified pipeline + monochrome
Turning point: unify the cut computation and refactor the brand accent to adaptive monochrome ink.
- Four audio correctors
Silences, pauses, fillers and repetitions, stacked on the unified pipeline.
- Repetitions with Apple Intelligence
Optional, gated AI layer on top of the deterministic n-gram detector.
- Free-form subtitles
Free position, size and animation by percentage; identical in preview and export.
- Branding + TestFlight
Icon Composer app icon and TestFlight upload for testing.
Takeaways
A single source of truth avoids preview/export divergence. A good, well-parameterized primitive yields more than one feature (pauses = silences with different parameters). Two layers (deterministic + AI) beat one: a deterministic floor guarantees universal quality and AI adds the hard cases only where it's available. And in an app whose content IS the video, accent-as-ink —not as a brand color— lets the content breathe and gives maximum-contrast CTAs for free.