Wszystkie strony Tworzenie środowiska

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"]
}
  • layout to stage (domyślnie: odtwarzacz bez zmian, a Twój pokaz za nim) albo immersive (Twój pokaz jest całym obrazem, a utwór staje się małą kartą).
  • requires wymienia to, co urządzenie musi mieć: webgl, webgl2 lub webgpu. 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.

Przykładowe środowisko Pulse w odtwarzaczu pełnoekranowym: pierścienie i małe widmo za okładką albumu i kolejką.

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.flash i rysuj S.flashAmt.
  • Szanuj ustawienia użytkownika. S.prefs.reduceFlashing oznacza też brak wstrząsów kamery i gwałtownego ruchu. Uwzględnij S.prefs.disableLasers, S.prefs.lowPower oraz poziom jakości tier przekazywany do resize.
  • Bądź funkcją czasu utworu. Wyliczaj wszystko z S.t i partytury, zamiast gromadzić stan klatka po klatce.
  • Zostań wewnątrz ctx. Rysuj w ctx.root i komunikuj się z Rainy przez ctx oraz @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.

Powiązane