Dokumentacja Zbuduj pokaz świateł
Tworzenie środowiska
Napisz własny pokaz świateł: mały pakiet JavaScript, który otrzymuje muzykę i decyduje, jak ona wygląda.
Środowisko to pakiet, który każdy może napisać, a każdy serwer Rainy zainstalować. Rainy wykonuje trudną część: analizuje utwór, trzyma rytm z dźwiękiem i sześćdziesiąt razy na sekundę przekazuje Twojemu kodowi opis muzyki. Twój kod rysuje.
Ta strona buduje środowisko od zera. Dokumentacja API środowisk wymienia wszystko, czego możesz użyć, a Jak działa pokaz świateł wyjaśnia, skąd biorą się dane.
Zanim zaczniesz
Potrzebujesz:
- Kopii repozytorium Rainy, którą możesz uruchomić lokalnie. Zobacz Instalacja.
- Kilku przeanalizowanych utworów w jej bibliotece.
- JavaScriptu. Środowisko to zwykłe moduły ES: bez kroku budowania i bez frameworka. Użyj płótna 2D, WebGL, DOM albo dowolnej biblioteki, którą umieścisz w pakiecie.
W repozytorium są dwa środowiska, które warto mieć otwarte obok tej strony:
packages/example-pulse: około 130 wierszy na płótnie 2D. Wszystko z tej strony w jednym pliku.packages/voyage: pełne środowisko 3D z three.js, własnym tekstem i układem immersyjnym.
Uruchom Rainy w trybie deweloperskim
Uruchom serwer z folderem niespakowanych pakietów:
RAINY_DEV=1 RAINY_ENVIRONMENTS_DEV=packages python app.py
Każdy folder w packages/, który zawiera poprawny manifest, pojawia się teraz jako środowisko z etykietą Wersja robocza i jest serwowany prosto z dysku, bez pamięci podręcznej. Zmień plik, odśwież stronę i zmiana już działa. Foldery, które nie przechodzą walidacji, są zgłaszane w konsoli serwera razem z powodem.
Tryb deweloperski jest przeznaczony dla Twojego komputera. Łagodzi też wymóg klucza sesji, więc nigdy nie używaj go na serwerze, do którego mają dostęp inne osoby.
1. Manifest
Utwórz packages/my-show/rainy-environment.json:
{
"manifest": 1,
"api": 1,
"id": "my-show",
"name": "My Show",
"version": "0.1.0",
"description": "One line for the picker",
"author": "You",
"entry": "index.js"
}
id identyfikuje Twoje środowisko na serwerze: małe litery, cyfry i łączniki. api to wersja API środowisk, dla której piszesz; obecnie jest to 1.
2. Najmniejsze środowisko
Utwórz packages/my-show/index.js. Domyślnym eksportem modułu jest klasa z czterema metodami: mount buduje to, czego potrzebujesz, wewnątrz ctx.root, Twojej własnej warstwy; unmount zwalnia to, czego strona nie zwolni za Ciebie; update w każdej klatce odczytuje muzykę, a render zaraz potem rysuje.
import { TAU, rgba } from '@rainy/lightshow';
export default class MyShow {
// Rainy hands you ctx. Build what you need inside ctx.root, your own layer.
mount(ctx) {
this.canvas = document.createElement('canvas');
this.canvas.style.cssText = 'display:block;width:100%;height:100%';
ctx.root.appendChild(this.canvas);
this.g = this.canvas.getContext('2d');
}
// Release what the page cannot. Rainy removes your layer itself.
unmount() {}
// Every frame, first: read the music, decide what happens.
update(S) {}
// Every frame, second: draw.
render(S) {
const { g, canvas } = this;
if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
}
g.fillStyle = rgba(S.palette.deep, 1);
g.fillRect(0, 0, canvas.width, canvas.height);
// A disc that jumps with the kick drum.
g.fillStyle = rgba(S.palette.a, S.dim);
g.beginPath();
g.arc(canvas.width / 2, canvas.height / 2, canvas.height * (0.1 + 0.1 * S.pulse.k), 0, TAU);
g.fill();
}
}
Odśwież Rainy, otwórz Ustawienia > Pokaz świateł, wybierz My Show, odtwórz utwór i włącz pokaz. Koło pulsuje razem ze stopą, w kolorze tonacji utworu.
@rainy/lightshow to jedyny moduł Rainy, który importuje pakiet. Zawiera funkcje pomocnicze do matematyki, koloru i czasu. Cała reszta przychodzi przez ctx i S.
3. Użyj muzyki
S to stan pokazu w tej klatce. Części, po które sięgniesz najpierw:
S.t |
Czas utworu w sekundach, w rytm dźwięku. |
S.pulse.k, .s, .h |
Stopa, werbel i hi-hat, od 0 do 1: skaczą w górę przy uderzeniu i gasną. |
S.F.bands |
Energia od 0 do 1 w pasmach sub, bass, mid, high i air oraz rms (głośność). |
S.F.newBeat, S.F.newBar |
Prawda w klatce, w której zaczyna się uderzenie lub takt. |
S.F.section.label |
intro, verse, build, chorus, drop, breakdown lub outro. |
S.F.buildProgress, S.F.dropIn |
Jak zaawansowane jest narastanie oraz liczba sekund do następnego dropu. |
S.palette |
Pięć kolorów dla tej sekcji: a, b, c, zabarwiona biel w i ciemny deep. |
S.I |
Intensywność od 0,3 do 1: jak mocno powinien grać pokaz. |
S.dim |
Główny ściemniacz. Pomnóż przez niego swoje światło. |
Na przykład pierścień w każdym takcie, który rozszerza się i gaśnie:
// in mount(): this.rings = [];
update(S) {
if (S.F.jumped) this.rings = []; // the user seeked: start clean
if (S.running && S.F.newBar) this.rings.push(S.t); // remember when, in song time
this.rings = this.rings.filter((born) => S.t - born < 2 && S.t >= born);
}
render(S) {
// … after the background …
const cx = canvas.width / 2, cy = canvas.height / 2;
for (const born of this.rings) {
const age = (S.t - born) / 2; // 0 when it starts, 1 when it is gone
g.strokeStyle = rgba(S.palette.b, (1 - age) * S.dim);
g.lineWidth = 3;
g.beginPath();
g.arc(cx, cy, canvas.height * (0.1 + 0.4 * age), 0, TAU);
g.stroke();
}
}
Zwróć uwagę, że pierścienie pamiętają czas utworu, a nie liczbę klatek. To nawyk, który warto wyrobić: wyliczaj to, co jest na ekranie, z S.t. Wtedy przewinięcie, pauza ani zgubiona klatka nie rozsynchronizują Twojego pokazu.
Patrz w przyszłość
setScore(score) daje Ci cały utwór naraz: każde uderzenie, każde uderzenie perkusji, każdą sekcję i energię każdego pasma pięćdziesiąt razy na sekundę. Właśnie tak Voyage pokazuje nadchodzącą muzykę jako teren przed Tobą. Dodaj tę metodę do swojej klasy i zachowaj partyturę:
setScore(score) {
this.score = score; // null while a song has no score yet
}
4. Błyski
Nigdy nie rób stroboskopu na własną rękę. Poproś o niego dyrygenta i narysuj to, na co pozwoli:
update(S) {
for (const ev of S.F.events) {
if (ev.type === 'drop') S.flash(1, S.palette.w);
}
}
render(S) {
// … your scene …
if (S.flashAmt > 0.01) {
g.fillStyle = rgba(S.flashColor, 0.6 * S.flashAmt);
g.fillRect(0, 0, canvas.width, canvas.height);
}
}
Dyrygent ogranicza błyski do trzech na sekundę i zamienia je w miękkie pulsy, gdy użytkownik ma włączoną opcję Ogranicz błyski. To ograniczenie chroni osoby z padaczką fotogenną. Działa tylko wtedy, gdy każde środowisko rysuje S.flashAmt, zamiast decydować samodzielnie.
5. Tekst
Gdy użytkownik ma tekst na scenie, S.lyricsOn ma wartość prawda, a S.lyrics mówi, co i kiedy jest śpiewane. O wyglądzie decydujesz Ty:
update(S) {
let text = '';
if (S.lyricsOn) {
const i = S.lyrics.lineIndex(S.t);
const line = i >= 0 ? S.lyrics.lines[i] : null;
if (line && !line.blank) {
// Each word has a start (s) and an end (e), in song time.
text = line.words.filter((w) => S.t >= w.s).map((w) => w.text).join(' ');
}
}
this.lineEl.textContent = text;
}
Trzymaj tekst z dala od elementów sterujących odtwarzacza: ctx.ui.safeArea() mówi, jaką część każdej strony sceny zasłaniają.
6. Układ i przyciski
Dodaj do manifestu dowolne z tych pól:
{
"layout": "immersive",
"requires": ["webgl2"]
}
layouttostage(domyślnie: odtwarzacz bez zmian, a Twój pokaz za nim) alboimmersive(Twój pokaz jest całym obrazem, a utwór staje się małą kartą).requireswymienia to, co urządzenie musi mieć:webgl,webgl2lubwebgpu. Na urządzeniu bez tego Twojego środowiska nie da się wybrać.
Aby dodać własny element sterujący, dodaj przycisk do paska pokazu:
this.button = ctx.ui.addButton({
icon: '<svg viewBox="0 0 24 24" width="14" height="14">…</svg>',
title: 'More rings',
onClick: () => { this.many = !this.many; this.button.setPressed(this.many); },
});
7. Zmień wygląd odtwarzacza
Twoje środowisko może zmienić wygląd samego odtwarzacza pełnoekranowego: jego kolory, rozmiary oraz to, co jest widoczne, a co ukryte. Robi się to arkuszem stylów, a nie JavaScriptem. Utwórz packages/my-show/style.css i podaj jego nazwę w manifeście:
{
"styles": ["style.css"]
}
Rainy wczytuje arkusz stylów, gdy Twoje środowisko wchodzi na scenę, i usuwa go, gdy z niej schodzi, więc nic z tego, co napiszesz, nie zostaje po Twoim pokazie.
Gdy jesteś na scenie, odtwarzacz ma klasę ls-env-my-show (ls-env- i Twoje id). Zaczynaj od niej każdą regułę. Paleta utworu jest dostępna jako zmienne CSS, więc odtwarzacz może przyjąć kolory muzyki:
/* The progress bar and the play button take the colours of the song. */
#fullscreen-player.ls-env-my-show .fs-progress-fill {
background-color: rgb(var(--ls-a));
}
#fullscreen-player.ls-env-my-show .fs-control-btn.play-pause {
background-color: rgb(var(--ls-w));
}
/* A glow around the artwork, in the second colour, at half strength. */
#fullscreen-player.ls-env-my-show .fs-artwork-container {
box-shadow: 0 0 70px rgb(var(--ls-b) / 0.55);
}
/* Hide the Rainy mark while this show is on. */
#fullscreen-player.ls-env-my-show .fs-brand {
display: none;
}
--ls-a, --ls-b i --ls-c to trzy kolory sekcji, --ls-w to zabarwiona biel, a --ls-deep to ciemny ton. Każda zawiera r g b, więc pisz rgb(var(--ls-a)) albo rgb(var(--ls-a) / 0.5) dla półprzezroczystości. W miarę trwania utworu płynnie przechodzą w kolory następnej sekcji.
Odtwarzacz mówi też, w jakim jest stanie, za pomocą klas, które możesz łączyć ze swoją: ls-idle, gdy układ immersyjny jest przygaszony, mode-standard lub mode-modern zależnie od trybu pełnoekranowego wybranego przez użytkownika, ls-lowpower dla Trybu niskiej wydajności. Pełna lista klas, części i zmiennych jest w sekcji Stylowanie odtwarzacza.
Trzy ograniczenia:
- Zostań w odtwarzaczu pełnoekranowym. Twoje reguły nie powinny pasować do niczego poza
#fullscreen-player.ls-env-my-show. - Zawsze zostaw wyjście. Nie ukrywaj ani nie zasłaniaj przycisku zamykania ani przycisku pokazu świateł i zadbaj, żeby elementy sterujące odtwarzaniem dało się obsługiwać.
- CSS dostaje paletę, a nie rytm. Wszystko, co porusza się z muzyką, należy do Twojej warstwy.
8. Obrazek
Ludzie wybierają środowisko po obrazku. Zapisz klatkę swojego pokazu jako obraz 16:9 (960 × 540 w zupełności wystarczy) i podaj jego nazwę w manifeście:
{
"preview": "preview.jpg"
}
Nie umieszczaj na nim okładki ani tekstu utworu, chyba że masz prawo je publikować.
9. Spakuj i zainstaluj
python scripts/pack_environment.py packages/my-show
To polecenie sprawdza folder dokładnie tak, jak serwer sprawdza przesłany plik, i zapisuje dist/my-show-0.1.0.zip. Ukryte pliki i folder tests/ są pomijane. Ten plik zip to Twoje środowisko: udostępnij go, a administrator zainstaluje go w Ustawienia > Pokaz świateł > Zainstalowane środowiska. Zobacz Instalowanie środowiska.
Aby wydać nową wersję, podnieś version w manifeście i spakuj ponownie. Zainstalowanie jej na starej wersji ją aktualizuje.

Pulse, przykład z packages/example-pulse, jako środowisko sceniczne: rysuje za odtwarzaczem, a jego arkusz stylów barwi odtwarzacz kolorami utworu; tutaj widać poświatę wokół okładki.
Zasady domu
- Błyski przechodzą przez dyrygenta. Wywołaj
S.flashi rysujS.flashAmt. - Szanuj ustawienia użytkownika.
S.prefs.reduceFlashingoznacza też brak wstrząsów kamery i gwałtownego ruchu. UwzględnijS.prefs.disableLasers,S.prefs.lowPoweroraz poziom jakościtierprzekazywany doresize. - Bądź funkcją czasu utworu. Wyliczaj wszystko z
S.ti partytury, zamiast gromadzić stan klatka po klatce. - Zostań wewnątrz
ctx. Rysuj wctx.rooti komunikuj się z Rainy przezctxoraz@rainy/lightshow. Nie sięgaj po inne elementy strony ani po API Rainy: dzięki temu Twój pakiet działa, gdy Rainy się zmienia, i dzięki temu Rainy będzie mógł w przyszłości uruchamiać pakiety w izolacji. - Zabierz swoje biblioteki ze sobą. Umieść w pakiecie to, co importujesz. Nie wczytuj kodu z innych serwerów.
- Zgłaszaj awarie wprost. Jeśli nie możesz kontynuować, na przykład po utracie kontekstu WebGL, wywołaj
ctx.fail(error). Rainy wróci do Klasycznego, a pokaz będzie trwał dalej.
Jeśli Twój kod zgłosi wyjątek, Rainy zrobi to samo: zdejmie środowisko ze sceny, usunie to, co dodało, i przełączy się na Klasyczne. Błąd trafia do konsoli przeglądarki.