Documentació Crea un espectacle de llums
Crear un entorn
Escriu el teu propi espectacle de llums: un petit paquet de JavaScript que rep la música i decideix quin aspecte té.
Un entorn és un paquet que qualsevol pot escriure i que qualsevol servidor Rainy pot instal·lar. Rainy fa la part difícil: analitza la cançó, porta el compàs amb l'àudio i lliura al teu codi una descripció de la música seixanta vegades per segon. El teu codi dibuixa.
En aquesta pàgina se'n construeix un des de zero. La Referència de l'API d'entorns enumera tot el que pots fer servir, i Com funciona l'espectacle de llums explica d'on surten les dades.
Abans de començar
Et cal:
- Una còpia del repositori de Rainy que puguis executar en local. Consulta Instal·lació.
- Unes quantes cançons analitzades a la seva biblioteca.
- JavaScript. Un entorn són mòduls ES i prou: sense pas de compilació ni framework. Fes servir un canvas 2D, WebGL, el DOM o qualsevol biblioteca que portis dins del paquet.
Hi ha dos entorns al repositori que convé tenir oberts al costat d'aquesta pàgina:
packages/example-pulse: unes 130 línies sobre un canvas 2D. Tot el d'aquesta pàgina, en un sol fitxer.packages/voyage: un entorn 3D complet amb three.js, les seves pròpies lletres i un disseny immersiu.
Iniciar Rainy en mode de desenvolupament
Inicia el servidor amb una carpeta de paquets sense empaquetar:
RAINY_DEV=1 RAINY_ENVIRONMENTS_DEV=packages python app.py
Cada carpeta de packages/ que contingui un manifest vàlid apareix ara com un entorn, amb l'etiqueta Desenvolupament, i se serveix directament des del disc, sense memòria cau. Canvia un fitxer, recarrega la pàgina, i el teu canvi ja funciona. Les carpetes que no passen la validació s'indiquen a la consola del servidor, amb el motiu.
El mode de desenvolupament és per a la teva pròpia màquina. També relaxa el requisit de la clau de sessió, de manera que no el facis servir mai en un servidor al qual accedeixin altres persones.
1. El manifest
Crea 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 és el que identifica el teu entorn en un servidor: lletres minúscules, dígits i guions. api és la versió de l'API d'entorns per a la qual escrius; avui és 1.
2. L'entorn més petit
Crea packages/my-show/index.js. L'exportació per defecte del mòdul és una classe amb quatre mètodes: mount construeix el que et cal dins de ctx.root, la teva pròpia capa; unmount allibera el que la pàgina no pot alliberar per tu; update llegeix la música a cada fotograma, i render dibuixa tot seguit.
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();
}
}
Recarrega Rainy, obre Configuració > Espectacle de llums, tria My Show, reprodueix una cançó i activa l'espectacle. El disc bomba amb el bombo, del color de la tonalitat de la cançó.
@rainy/lightshow és l'únic mòdul de Rainy que importa un paquet. Té utilitats de matemàtiques, color i temps. Tota la resta arriba a través de ctx i S.
3. Fer servir la música
S és l'estat de l'espectacle en aquest fotograma. Les parts a les quals recorreràs primer:
S.t |
El temps de la cançó en segons, al compàs de l'àudio. |
S.pulse.k, .s, .h |
Bombo, caixa i xarles, de 0 a 1: pugen de cop amb cada cop i s'apaguen. |
S.F.bands |
Energia de 0 a 1 a sub, bass, mid, high i air, a més de rms (volum). |
S.F.newBeat, S.F.newBar |
Cert al fotograma en què comença un pols o un compàs. |
S.F.section.label |
intro, verse, build, chorus, drop, breakdown o outro. |
S.F.buildProgress, S.F.dropIn |
Quant s'ha avançat en una pujada, i segons fins al proper drop. |
S.palette |
Cinc colors per a aquesta secció: a, b, c, un blanc tenyit w i un de fosc deep. |
S.I |
Intensitat de 0,3 a 1: amb quina força ha d'anar l'espectacle. |
S.dim |
L'atenuador general. Multiplica la teva llum per ell. |
Per exemple, un anell a cada compàs que s'expandeix i s'apaga:
// 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();
}
}
Fixa't que els anells recorden el temps de la cançó, no un nombre de fotogrames. Aquest és l'hàbit que cal agafar: calcula el que hi ha a la pantalla a partir de S.t. Així, un salt, una pausa o un fotograma perdut no poden deixar el teu espectacle descompassat.
Mirar endavant
setScore(score) et dona la cançó sencera de cop: cada pols, cada cop de bateria, cada secció i l'energia de cada banda cinquanta vegades per segon. Així és com Voyage mostra la música que ve com el terreny que tens al davant. Afegeix el mètode a la teva classe i desa la partitura:
setScore(score) {
this.score = score; // null while a song has no score yet
}
4. Flaixos
No facis mai estrobos pel teu compte. Demana'ls al director i dibuixa el que permeti:
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);
}
}
El director limita els flaixos a tres per segon i els converteix en polsos suaus quan l'usuari té activat Redueix els flaixos. Aquest límit protegeix les persones amb epilèpsia fotosensible. Només funciona si tots els entorns dibuixen S.flashAmt en lloc de decidir pel seu compte.
5. Lletres
Quan l'usuari té les lletres a escena, S.lyricsOn és cert i S.lyrics et diu què es canta i quan. L'aspecte el decideixes tu:
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;
}
Mantén el text fora dels controls del reproductor: ctx.ui.safeArea() indica quina part de cada costat de l'escenari cobreixen.
6. Disseny i botons
Afegeix qualsevol d'aquests camps al manifest:
{
"layout": "immersive",
"requires": ["webgl2"]
}
layoutésstage(el valor per defecte: el reproductor tal com és, amb el teu espectacle al darrere) oimmersive(el teu espectacle és tota la imatge, i la cançó passa a ser una targeta petita).requiresenumera el que el dispositiu ha de tenir:webgl,webgl2owebgpu. En un dispositiu que no ho tingui, el teu entorn no es pot triar.
Per a un control propi, afegeix un botó a la barra de l'espectacle:
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. Canviar l'estil del reproductor
El teu entorn pot canviar l'aspecte del mateix reproductor a pantalla completa: els seus colors, les seves mides, què es mostra i què s'amaga. Això es fa amb un full d'estil, no amb JavaScript. Crea packages/my-show/style.css i posa-li nom al manifest:
{
"styles": ["style.css"]
}
Rainy carrega el full d'estil quan el teu entorn surt a escena i el retira quan se'n va, de manera que res del que escriguis sobreviu al teu espectacle.
Mentre ets a escena, el reproductor té la classe ls-env-my-show (ls-env- i el teu id). Comença totes les regles amb ella. La paleta de la cançó hi és com a variables CSS, de manera que el reproductor pot agafar els colors de la música:
/* 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 són els tres colors de la secció, --ls-w és un blanc tenyit i --ls-deep un to fosc. Cadascuna conté r g b, de manera que escriu rgb(var(--ls-a)), o rgb(var(--ls-a) / 0.5) per a una transparència del 50 %. Llisquen cap als colors de la secció següent a mesura que avança la cançó.
El reproductor també et diu en quin estat és, amb classes que pots combinar amb la teva: ls-idle mentre un disseny immersiu està atenuat, mode-standard o mode-modern segons el mode de pantalla completa que ha triat l'usuari, ls-lowpower per al Mode de baix rendiment. La llista completa de classes, parts i variables és a Donar estil al reproductor.
Tres límits:
- Queda't dins del reproductor a pantalla completa. Les teves regles no han d'afectar res fora de
#fullscreen-player.ls-env-my-show. - Deixa sempre una sortida. No amaguis ni tapis el botó de tancar ni el de l'espectacle de llums, i mantén utilitzables els controls de reproducció.
- El CSS rep la paleta, no el pols. Tot el que es mou amb la música va a la teva capa.
8. Una imatge
La gent tria un entorn per la seva imatge. Desa un fotograma del teu espectacle com a imatge 16:9 (960 × 540 és de sobres) i posa-li nom al manifest:
{
"preview": "preview.jpg"
}
Deixa'n fora la caràtula i la lletra d'una cançó, tret que tinguis dret a publicar-les.
9. Empaquetar i instal·lar
python scripts/pack_environment.py packages/my-show
Això comprova la carpeta exactament igual que un servidor comprova una pujada i escriu dist/my-show-0.1.0.zip. Els fitxers ocults i la carpeta tests/ es deixen fora. Aquest zip és el teu entorn: comparteix-lo, i un administrador l'instal·la a Configuració > Espectacle de llums > Entorns instal·lats. Consulta Instal·lar un entorn.
Per publicar una versió nova, apuja version al manifest i torna a empaquetar. Instal·lar-la sobre l'anterior l'actualitza.

Pulse, l'exemple de packages/example-pulse, com a entorn d'escenari: dibuixa darrere del reproductor, i el seu full d'estil tenyeix el reproductor amb els colors de la cançó; aquí, la resplendor al voltant de la caràtula.
Regles de la casa
- Els flaixos passen pel director. Crida
S.flashi dibuixaS.flashAmt. - Respecta la configuració de l'usuari.
S.prefs.reduceFlashingtambé vol dir res de sacsejades de càmera ni moviments bruscos. RespectaS.prefs.disableLasers,S.prefs.lowPoweri el nivell de qualitattierque represize. - Sigues una funció del temps de la cançó. Calcula a partir de
S.ti de la partitura en lloc d'acumular estat fotograma a fotograma. - Queda't dins de
ctx. Dibuixa actx.rooti parla amb Rainy a través dectxi@rainy/lightshow. No toquis els altres elements de la pàgina ni l'API de Rainy: això és el que manté el teu paquet funcionant quan Rainy canvia, i el que permetrà a Rainy executar paquets aïllats en el futur. - Porta les teves biblioteques. Posa dins del paquet el que importis. No carreguis codi d'altres servidors.
- Falla de manera visible. Si no pots continuar, per exemple després de perdre el context de WebGL, crida
ctx.fail(error). Rainy torna a Clàssic i l'espectacle continua.
Si el teu codi llança una excepció, Rainy fa el mateix: retira l'entorn de l'escenari, treu el que havia afegit i canvia a Clàssic. L'error queda a la consola del navegador.