Caso de estudio · 2026

noise.

Editor de video on-device

Trabajas encima del crudo: el original nunca se toca y solo al exportar nace el video limpio.

Plataforma
iPhone · iPad
iOS mínimo
26+
Dependencias
0
Correctores
5

Contexto

noise es un editor de video nativo para iOS/iPadOS que limpia automáticamente grabaciones habladas (reels, vlogs, tutoriales, talking-heads). En vez de cortar a mano, el usuario activa una serie de «correctores» que detectan y eliminan lo que sobra del audio —silencios, pausas largas, muletillas, repeticiones y tartamudeos— y, además, genera subtítulos automáticos quemados en el video.

Es una herramienta para agilizar el primer pase de limpieza —el más mecánico y tedioso— antes de pasar el material a la edición final. No sustituye a la suite de edición: le quita el trabajo aburrido.

Todo el procesamiento ocurre en el dispositivo: el audio nunca sube a la nube. Privacidad como feature, no como promesa.

El reto

El reto de arquitectura más grande: cinco correctores distintos tienen que combinarse y producir UN solo resultado que alimente a la vez el preview (AVPlayer) y el export (AVAssetExportSession). Si preview y export divergen, «lo que ves» deja de ser «lo que se guarda». A eso se suma detectar silencios sin librerías (leyendo PCM a mano), transcribir on-device con la API nueva de iOS 26 (sin el límite de 1 minuto del viejo SFSpeechRecognizer) y mapear dos líneas de tiempo —fuente y composición— porque los cortes reordenan el tiempo de todo lo anclado a él.

La solución

Una sola fuente de verdad para los cortes: todos los correctores aportan los tramos que quieren eliminar y un único método (computeCuts) los une y deriva el complemento → los rangos a conservar. Esa misma lista la consumen el preview y el exporter. La detección de silencios se hace por energía (RMS→dB por ventana de 20 ms) sin ML ni terceros, y «pausas largas» reusa el mismo detector con otros parámetros: un corrector entero gratis.

/// Ú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)) }
}
El pipeline de cortes unificado: un solo método combina todos los correctores activos; preview y export consumen la misma lista de rangos.
💡

El preview ES el export: la misma AVMutableComposition alimenta lo que ves y lo que se guarda. Cero sorpresas al exportar.

La app

Arquitectura y stack

100% nativo, cero dependencias de terceros. SwiftUI para toda la UI; AVFoundation para lectura PCM, composición y export; Speech (iOS 26) con SpeechAnalyzer para transcripción on-device con timing por palabra; FoundationModels (Apple Intelligence) on-device para repeticiones semánticas opcionales; Core Animation para quemar subtítulos animados. Persistencia ligera en JSON local: un proyecto es una referencia al video de Fotos más sus ajustes —no se copia el video.

// 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
}
Detección de silencios por energía (RMS→dB) leyendo muestras PCM a mano. Sin librerías ni ML: por debajo del umbral = silencio a cortar.
ℹ️

Cinco correctores combinables en un solo pipeline de cortes; añadir uno nuevo es una rama más. La arquitectura está pensada para crecer.

Apple Intelligence, on-device

Las repeticiones se detectan en dos capas. Una capa determinista (n-grama) garantiza un suelo de calidad en todo dispositivo; encima, una pasada opcional y gated de Apple Intelligence capta redundancias semánticas que el n-grama no ve («quiero ir… mejor vamos»). El modelo on-device devuelve los índices a eliminar mediante salida estructurada (guided generation), y nunca corta solo: cada sugerencia entra en una lista revisable.

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
}
Guided generation con @Generable: el modelo on-device está obligado a devolver una salida tipada con los tramos redundantes a eliminar.

Entregables

  • Corrector de silencios (RMS→dB configurable)
  • Corrector de pausas largas (acota a un máximo)
  • Corrector de muletillas (léxico es/en, lista revisable)
  • Corrector de repeticiones (n-grama + Apple Intelligence opcional)
  • Subtítulos automáticos on-device (timing por palabra)
  • 6 animaciones de subtítulo (karaoke, una palabra, pop…)
  • Preview WYSIWYG (preview = export)
  • Edición no destructiva (referencia a Fotos)

Cómo creció

  1. Arranque + port del motor

    El motor de edición (silencios + export) se portó desde un proyecto previo, ideas365.

  2. Pipeline unificado + monocromo

    Punto de inflexión: unificar el cálculo de cortes y refactorizar el acento de marca a tinta monocroma adaptativa.

  3. Cuatro correctores de audio

    Silencios, pausas, muletillas y repeticiones, apilados sobre el pipeline unificado.

  4. Repeticiones con Apple Intelligence

    Capa de IA opcional y gated sobre el detector determinista n-grama.

  5. Subtítulos libres

    Posición, tamaño y animación libres por porcentaje; idénticos en preview y export.

  6. Branding + TestFlight

    Ícono con Icon Composer y subida a TestFlight para pruebas.

Aprendizajes

Una sola fuente de verdad evita la divergencia preview/export. Un buen primitivo bien parametrizado rinde más de una feature (pausas = silencios con otros parámetros). Dos capas (determinista + IA) > una sola: un suelo determinista garantiza calidad universal y la IA añade los casos difíciles solo donde está disponible. Y en una app cuyo contenido ES el video, el acento como tinta —no como color de marca— deja respirar el contenido y da CTA de máximo contraste gratis.

¿Construimos algo juntos?