# Jak przekształcić motyw dla odtwarzacza muzycznego w dodatek runtime z WASM i efektami reagującymi na audio
Projekt rozpoczął się jako próba naprawy uszkodzonego motywu dla popularnego odtwarzacza Electron, a zakończył stworzeniem pełnoprawnego dodatku runtime ChromaSync — z dynamiczną adaptacją kolorów, pulsacją reagującą na audio, trybem Zen i rdzeniem obliczeniowym opartym na WebAssembly. Wszystko to działa na bazie PulseSync — platformy do modyfikacji interfejsu za pomocą JS/CSS, gdzie autor napotkał ograniczenia frameworka i konieczność skalowania logiki poza CSS.
Architektura: od motywu do orkiestratora
ChromaSync początkowo miał być wizualną łatką, ale szybko przerodził się w wielowarstwowy system. Kluczowe komponenty:
metadata.json— punkt wejścia dla PulseSync;handleEvents.json— deklaratywne opisanie ustawień;script.js— centralny orkiestrator stanu i efektów;style.css— podstawowe style i animacje;- moduł WASM — rdzeń do ciężkich obliczeń (pulsacja, wygładzanie);
- warstwa backendowa — opcjonalne funkcje sieciowe.
Połączenie PulseSync → handleEvents → script.js → DOM/CSS/WASM pozwala na dynamiczne stosowanie zmian bez przeładowania. Na przykład przy zmianie utworu skrypt wyciąga dominujące kolory z okładki, przekazuje je do zmiennych CSS i jednocześnie uruchamia pulsację reagującą na audio za pośrednictwem rdzenia WASM. Wszystko to dzieje się w czasie rzeczywistym, bez zauważalnych opóźnień.
System ustawień: 70 parametrów pod kontrolą
PulseSync pozwala użytkownikom konfigurować dodatki bez edycji kodu. Każde ustawienie jest opisane w strukturze JSON z obowiązkowym id, za pomocą którego łączy się z logiką runtime. Przykładowa konfiguracja trybu zmiany tła:
{
"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"
}
Gdy takich parametrów jest 70, pojawia się potrzeba normalizacji stanu. script.js przejmuje to zadanie: zbiera wartości, weryfikuje je, przekształca w jednolity obiekt stanu i stosuje do interfejsu. Bez tej warstwy system stałby się nie do opanowania — zwłaszcza przy współzależnych ustawieniach, na przykład gdy włączenie „Zen Mode” automatycznie wyłącza cząstki i obniża intensywność pulsacji.
Ewolucja script.js: od 700 do ponad 8000 linii
Początkowo script.js był prostym plikiem liczącym 700 linii. Dziś przekroczył 8000 linii — nie z powodu nadęcia, lecz potrzeby koordynacji coraz większej liczby podsystemów. Ten plik pełni rolę orkiestratora:
- Odbiera i normalizuje ustawienia z PulseSync;
- Śledzi stan odtwarzacza (utwór, postęp, pełny ekran);
- Zarządza elementami DOM i zmiennymi CSS;
- Uruchamia i zatrzymuje efekty reagujące na audio;
- Wywołuje funkcje WASM do obliczeń matematycznych;
- Obsługuje zdarzenia myszy i klawiatury dla paralaksy i interaktywności.
Przykładowa wczesna logika — paralaksa tła na podstawie ruchu myszy:
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}`;
});
};
Później pojawiły się bardziej złożone funkcje — na przykład dynamiczne wyciąganie palety kolorów z okładki utworu:
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: kiedy JavaScript już nie wystarcza
Przełomowy moment nastąpił, gdy pulsacja przestała być prostą animacją i stała się systemem obciążonym obliczeniowo z wieloma trybami, wygładzaniem i wyzwalaczami. JavaScript nie radził sobie z wymaganą wydajnością — zwłaszcza na słabszych urządzeniach. Rozwiązanie — przeniesienie rdzenia do Rust/WASM.
Ładowanie modułu:
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;
}
Przykładowa eksportowana funkcja w Rust:
#[no_mangle]
pub extern "C" fn c2(prev: f32, current: f32, weight: f32) -> f32 {
prev * weight + current * (1.0 - weight)
}
WASM jest używany do:
- Obliczania amplitudy ścieżki dźwiękowej w czasie rzeczywistym;
- Płynnego wygładzania przejść między klatkami;
- Optymalizacji częstotliwości wywołań klatek animacji;
- Izolacji ciężkiej logiki od głównego wątku UI.
Rezultat — stabilne 60 FPS nawet przy aktywnych efektach i złożonych filtrach, takich jak pikselizacja czy szum.
Co ważne
- PulseSync — platforma umożliwiająca przekształcanie motywów w pełnoprawne dodatki runtime bez dostępu do kodu źródłowego odtwarzacza.
- WASM jest kluczowy dla efektów reagujących na audio: zapewnia wydajność niedostępną w czystym JavaScript.
- script.js — to nie zwykły skrypt, lecz orkiestrator łączący ustawienia, DOM, CSS i rdzeń obliczeniowy.
- Dynamiczna adaptacja kolorów z okładki utworu realizowana jest poprzez analizę obrazu w runtime i wstrzykiwanie zmiennych CSS.
- Architektura projektu pozwala na skalowanie: nowe efekty dodawane są jako niezależne moduły, bez naruszania istniejącej logiki.
Optymalizacja: piękno nie może kosztować FPS
Każdy nowy efekt to ryzyko spadku wydajności. Autor kładzie szczególny nacisk na profilowanie i optymalizację:
- Użycie
requestAnimationFramezamiastsetTimeoutdo animacji; - Debouncing zdarzeń myszy i przewijania;
- Leniva inicjalizacja ciężkich modułów (np. WASM ładowany tylko przy aktywacji pulsacji);
- Wyłączanie nieużywanych efektów w trybie tła;
- Buforowanie wyników analizy okładek utworów.
Testy wykazały, że nawet przy włączonych wszystkich efektach (pulsacja, cząstki, paralaksa, filtry) zużycie CPU nie przekracza 8–12% na średnim laptopie. Dzięki temu interfejs pozostał responsywny, a animacje płynne.
Przyszłość: w jakim kierunku iść
Autor rozważa kilka kierunków rozwoju:
- Integracja z systemowymi API do sterowania głośnością i klawiszami multimedialnymi;
- Obsługa niestandardowych shaderów poprzez WebGL;
- Eksport ustawień do chmury i synchronizacja między urządzeniami;
- Automatyczna adaptacja efektów do gatunku muzyki (np. bardziej agresywna pulsacja dla elektroniki, płynne przejścia dla jazzu).
Główna zasada — nie dodawać funkcji dla samej funkcji. Każdy nowy element musi rozwiązywać konkretny problem użytkownika lub znacząco poprawiać doświadczenie bez szkody dla wydajności.
— Editorial Team
Brak komentarzy.