Volver al inicio

Creando un addon en tiempo de ejecución con WASM para un reproductor de música

Desglose detallado de la creación del addon en tiempo de ejecución ChromaSync para un reproductor de música: desde la restauración del tema hasta la implementación del núcleo WASM para efectos reactivos al audio. Cubre arquitectura, sistema de ajustes, optimización y direcciones de desarrollo futuro.

Addon en tiempo de ejecución con WASM: cómo reconstruí la arquitectura para efectos reactivos al audio
Advertisement 728x90

Transformando un tema de reproductor de música en un complemento en tiempo de ejecución con WASM y efectos reactivos al audio

El proyecto comenzó como un intento de arreglar un tema roto para un popular reproductor de música en Electron, pero evolucionó hasta convertirse en un complemento completo en tiempo de ejecución llamado ChromaSync—con adaptación dinámica de colores, pulsaciones reactivas al audio, Modo Zen y un núcleo computacional en WebAssembly. Todo esto se ejecuta sobre PulseSync—una plataforma para personalizar la interfaz mediante JS/CSS—donde el autor se topó con las limitaciones del framework y necesitó escalar la lógica más allá de CSS.

Arquitectura: De tema a orquestador

ChromaSync se concibió originalmente como un parche visual, pero rápidamente creció hasta convertirse en un sistema multicapa. Componentes clave:

  • metadata.json — punto de entrada para PulseSync;
  • handleEvents.json — descripción declarativa de configuraciones;
  • script.js — orquestador central para el estado y efectos;
  • style.css — estilos base y animaciones;
  • Módulo WASM — núcleo para cálculos pesados (pulsaciones, suavizado);
  • Capa backend — funciones opcionales de red.

El pipeline PulseSync → handleEvents → script.js → DOM/CSS/WASM permite cambios dinámicos sin recargas. Por ejemplo, al cambiar de pista, el script extrae los colores dominantes de la portada del álbum, los pasa a variables CSS y simultáneamente inicia las pulsaciones reactivas al audio mediante el núcleo WASM. Todo en tiempo real, sin retrasos perceptibles.

Google AdInline article slot

Sistema de configuración: 70 parámetros bajo control

PulseSync permite a los usuarios ajustar complementos sin tocar el código. Cada configuración se define en una estructura JSON con un id requerido que la vincula a la lógica en tiempo de ejecución. Ejemplo de configuración para el modo de transición de fondo:

{
  "id": "backgroundTrackTransitionMode",
  "name": "Withmena fona (trek)",
  "description": "Effekt switching fonovoy oblozhki when smene treka: fade (plavnaya podmena) or slide (staraya uezzhaet vlevo, novaya vezzhaet right).",
  "type": "selector",
  "selected": "1",
  "options": [
      {
          "name": "Fade (by umolchaniyu)",
          "id": "fade"
      },
      {
          "name": "Slide (left/right)",
          "id": "slide-horizontal"
      }
  ],
  "defaultParameter": "fade"
}

Con 70 parámetros de este tipo, la normalización del estado resulta esencial. script.js se encarga de esto: recopila valores, los valida, los convierte en un objeto de estado unificado y los aplica a la interfaz. Sin esta capa, el sistema sería ingobernable—especialmente con configuraciones interdependientes, como el Modo Zen que desactiva automáticamente las partículas y atenúa la intensidad de las pulsaciones.

Evolución de script.js: De 700 líneas a un monolito

script.js comenzó como un archivo simple de 700 líneas. Ahora supera las 8.000—no es hinchazón, sino coordinación de subsistemas en constante crecimiento. Actúa como el orquestador:

Google AdInline article slot
  • Recupera y normaliza configuraciones de PulseSync;
  • Rastrea el estado del reproductor (pista, progreso, pantalla completa);
  • Gestiona elementos DOM y variables CSS;
  • Inicia/detiene efectos reactivos al audio;
  • Llama a funciones WASM para cálculos matemáticos pesados;
  • Maneja eventos de ratón/teclado para paralaje e interactividad.

Ejemplo temprano: paralaje de fondo basado en el movimiento del ratón:

const handleParallax = (event) => {
    const offsetX = event.clientX / window.innerWidth - 0.5;
    const offsetY = event.clientY / window.innerHeight - 0.5;
    const posX = `calc(50% + ${offsetX * 25}px)`;
    const posY = `calc(50% + ${offsetY * 25}px)`;

    requestAnimationFrame(() => {
        bgLayer1.style.backgroundPosition = `${posX} ${posY}`;
        bgLayer2.style.backgroundPosition = `${posX} ${posY}`;
    });
};

Más adelante, surgieron funciones más avanzadas—como la extracción dinámica de paletas de colores de la portada de la pista:

const applyChameleonStyles = (palette, sourceChoice) => {
    if (settings.useCustomAccentColor?.value) {
        document.documentElement.style.setProperty(
            '--accent-color',
            settings.customAccentColor?.value || '#8a63b3'
        );
        return;
    }

    const swatch = palette?.[sourceChoice] || palette?.Vibrant;
    if (!swatch) return;
    document.documentElement.style.setProperty('--accent-color', swatch.getHex());
};

WASM: Cuando JavaScript no da la talla

El punto de inflexión llegó cuando las pulsaciones evolucionaron de una animación simple a un sistema intensivo en cómputo con múltiples modos, suavizado y disparadores. JavaScript no podía satisfacer las demandas de rendimiento—especialmente en dispositivos de gama baja. Solución: trasladar el núcleo a Rust/WASM.

Google AdInline article slot

Carga del módulo:

async function loadPulseWasm() {
    const res = await fetch('/api/chromasync/rust', { cache: 'no-store' });
    const buf = await res.arrayBuffer();
    const { instance } = await WebAssembly.instantiate(buf);
    return instance.exports;
}

Ejemplo de función exportada en Rust:

#[no_mangle]
pub extern "C" fn c2(prev: f32, current: f32, weight: f32) -> f32 {
    prev * weight + current * (1.0 - weight)
}

WASM se encarga de:

  • Cálculo en tiempo real de la amplitud de la pista;
  • Transiciones suaves entre frames;
  • Optimización de la tasa de frames de animación;
  • Aislamiento de lógica pesada del hilo principal de la UI.

Resultado: 60 FPS estables incluso con efectos activos y filtros complejos como pixelado o ruido.

Lecciones clave

  • PulseSync — plataforma para convertir temas en complementos completos en tiempo de ejecución sin acceso al código fuente del reproductor.
  • WASM es crucial para efectos reactivos al audio: ofrece un rendimiento que JavaScript puro no iguala.
  • script.js — más que un script; es el orquestador que une configuraciones, DOM, CSS y núcleo de cómputo.
  • La adaptación dinámica de colores desde la portada de la pista usa análisis de imágenes en tiempo de ejecución y variables CSS.
  • La arquitectura es escalable: nuevos efectos se añaden como módulos independientes sin romper la lógica existente.

Optimización: La belleza no debe costar FPS

Cada nuevo efecto pone en riesgo caídas de rendimiento. El autor se centra en el perfilado y la optimización:

  • requestAnimationFrame en vez de setTimeout para animaciones;
  • Debouncing de eventos de ratón y scroll;
  • Carga perezosa de módulos pesados (p. ej., WASM solo al activar pulsaciones);
  • Desactivación de efectos no usados en segundo plano;
  • Caché de resultados de análisis de portadas de álbumes.

Las pruebas muestran que el uso de CPU se mantiene por debajo del 8–12% en portátiles promedio incluso con todos los efectos activados (pulsaciones, partículas, paralaje, filtros). Esto mantiene la UI responsiva y las animaciones suaves como la mantequilla.

El futuro: Hacia dónde ir ahora

El autor baraja varias direcciones de desarrollo:

  • Integración con APIs del sistema para control de volumen y teclas multimedia;
  • Soporte para shaders de usuario vía WebGL;
  • Exportación/sincronización en la nube de configuraciones entre dispositivos;
  • Efectos que se autoadaptan al género musical (p. ej., pulsaciones agresivas para EDM, transiciones suaves para jazz).

Principio fundamental: no añadir funciones por añadir. Cada adición debe resolver un problema real del usuario o mejorar la experiencia sin sacrificar rendimiento.

— Editorial Team

Advertisement 728x90

Leer después