pixi-effects 0.27.2 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +6 -6
- package/ai/chat-template.html +3 -3
- package/ai/tools/check.mjs +30 -0
- package/dist/{Base-CKGl6jCo.d.ts → Base-C6f0fE8j.d.ts} +1 -1
- package/dist/{Base-DjhdL8B1.d.cts → Base-DI1FCGcH.d.cts} +1 -1
- package/dist/Composition-HFEVIYHN.js +7 -0
- package/dist/{Composition-TZHVYIGH.js.map → Composition-HFEVIYHN.js.map} +1 -1
- package/dist/Composition-NI74UJT3.cjs +16 -0
- package/dist/{Composition-DRCTWIYH.cjs.map → Composition-NI74UJT3.cjs.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-CbYwTs6U.d.cts → Movie-CHcM8HNB.d.ts} +3 -1
- package/dist/{Movie-ILKq2fw_.d.ts → Movie-Cx5ozUGb.d.cts} +3 -1
- package/dist/Presenter.d.cts +2 -2
- package/dist/Presenter.d.ts +2 -2
- package/dist/chunk-4LEX3G3M.cjs +259 -0
- package/dist/chunk-4LEX3G3M.cjs.map +1 -0
- package/dist/{chunk-BQNNZEAX.js → chunk-AK54FIWM.js} +7 -4
- package/dist/chunk-AK54FIWM.js.map +1 -0
- package/dist/{chunk-YOV6MEXH.cjs → chunk-MZCDBRTY.cjs} +7 -4
- package/dist/chunk-MZCDBRTY.cjs.map +1 -0
- package/dist/{chunk-C7HJJ7MZ.cjs → chunk-SBOVUZZY.cjs} +61 -61
- package/dist/{chunk-C7HJJ7MZ.cjs.map → chunk-SBOVUZZY.cjs.map} +1 -1
- package/dist/{chunk-K3F2YKL4.js → chunk-WEJAQAFX.js} +3 -3
- package/dist/{chunk-K3F2YKL4.js.map → chunk-WEJAQAFX.js.map} +1 -1
- package/dist/chunk-ZHUUIF6A.js +250 -0
- package/dist/chunk-ZHUUIF6A.js.map +1 -0
- package/dist/events-EE3SM5LM.cjs +16 -0
- package/dist/events-EE3SM5LM.cjs.map +1 -0
- package/dist/events-KXBWR4IV.js +3 -0
- package/dist/events-KXBWR4IV.js.map +1 -0
- package/dist/index.cjs +49 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +18 -7
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/{music-6MTHQEXS.cjs → music-6WFTPVHU.cjs} +43 -202
- package/dist/music-6WFTPVHU.cjs.map +1 -0
- package/dist/{music-72YJAMXH.js → music-JKOM5YK6.js} +8 -192
- package/dist/music-JKOM5YK6.js.map +1 -0
- package/dist/three.cjs +5 -5
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +1 -1
- package/dist/{types-BNVsBBZf.d.ts → types-DpK1vwcl.d.cts} +79 -1
- package/dist/{types-BNVsBBZf.d.cts → types-DpK1vwcl.d.ts} +79 -1
- package/docs/api.md +28 -0
- package/docs/dsl.md +8 -4
- package/llms-full.txt +39 -6
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +1 -1
- package/skills/pixi-effects/reference/pitfalls.md +1 -0
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-DRCTWIYH.cjs +0 -16
- package/dist/Composition-TZHVYIGH.js +0 -7
- package/dist/chunk-BQNNZEAX.js.map +0 -1
- package/dist/chunk-YOV6MEXH.cjs.map +0 -1
- package/dist/music-6MTHQEXS.cjs.map +0 -1
- package/dist/music-72YJAMXH.js.map +0 -1
|
@@ -172,6 +172,80 @@ declare function pictureStops(stops: readonly Stop[], opts?: {
|
|
|
172
172
|
pdf?: boolean;
|
|
173
173
|
}): Stop[];
|
|
174
174
|
|
|
175
|
+
/** One note or chord of a track, as a score reader sees it. */
|
|
176
|
+
interface MusicNote {
|
|
177
|
+
/** Where it starts: beats from the start, and the same moment in seconds (the tempo map applied; swing and humanize are not). */
|
|
178
|
+
beat: number;
|
|
179
|
+
time: number;
|
|
180
|
+
/** How long it is held, in beats and in seconds. */
|
|
181
|
+
duration: number;
|
|
182
|
+
seconds: number;
|
|
183
|
+
/** The bar it starts in (1-based, from `meter`) and its beat inside that bar (1 = the downbeat, 1.5 = the "and" of 1). */
|
|
184
|
+
bar: number;
|
|
185
|
+
beatInBar: number;
|
|
186
|
+
/** The audible MIDI notes (`music.transpose` and the track's `transpose` are in). */
|
|
187
|
+
midi: number[];
|
|
188
|
+
/** What the score called it: `Am7`, `c4`, `[c4 e4 g4]`. */
|
|
189
|
+
name: string;
|
|
190
|
+
/** More than one note at once. */
|
|
191
|
+
chord: boolean;
|
|
192
|
+
/** 0–1 (0.8 normal, 1 accented with `!`, 0.55 soft with `,`). */
|
|
193
|
+
velocity: number;
|
|
194
|
+
}
|
|
195
|
+
interface MusicTrackEvents {
|
|
196
|
+
inst: string;
|
|
197
|
+
/** The track's length in beats and in bars (not a whole number when a bar is short: that is what to look for). */
|
|
198
|
+
beats: number;
|
|
199
|
+
bars: number;
|
|
200
|
+
notes: MusicNote[];
|
|
201
|
+
}
|
|
202
|
+
interface MusicDrumHit {
|
|
203
|
+
beat: number;
|
|
204
|
+
time: number;
|
|
205
|
+
bar: number;
|
|
206
|
+
beatInBar: number;
|
|
207
|
+
velocity: number;
|
|
208
|
+
}
|
|
209
|
+
interface MusicDrumEvents {
|
|
210
|
+
kind: string;
|
|
211
|
+
hits: MusicDrumHit[];
|
|
212
|
+
}
|
|
213
|
+
interface MusicEvents {
|
|
214
|
+
/** The tempo at the start. */
|
|
215
|
+
bpm: number;
|
|
216
|
+
meter: number;
|
|
217
|
+
beats: number;
|
|
218
|
+
bars: number;
|
|
219
|
+
/** The music without its reverb tail. */
|
|
220
|
+
seconds: number;
|
|
221
|
+
tracks: MusicTrackEvents[];
|
|
222
|
+
/** Every drum that is hit, over all sections, with every hit written out. */
|
|
223
|
+
drums: MusicDrumEvents[];
|
|
224
|
+
}
|
|
225
|
+
/** What the `check` tool prints about a music layer: lengths in bars (a short track shows), what starts in each bar, where the drums play. */
|
|
226
|
+
interface MusicSummary {
|
|
227
|
+
bpm: number;
|
|
228
|
+
meter: number;
|
|
229
|
+
/** The score set `meter`; when false 4 is only assumed. */
|
|
230
|
+
meterSet: boolean;
|
|
231
|
+
beats: number;
|
|
232
|
+
bars: number;
|
|
233
|
+
seconds: number;
|
|
234
|
+
tail: number;
|
|
235
|
+
tracks: Array<{
|
|
236
|
+
inst: string;
|
|
237
|
+
beats: number;
|
|
238
|
+
bars: number;
|
|
239
|
+
events: number;
|
|
240
|
+
perBar: string[];
|
|
241
|
+
}>;
|
|
242
|
+
drums: Array<{
|
|
243
|
+
from: number;
|
|
244
|
+
to: number;
|
|
245
|
+
kinds: string[];
|
|
246
|
+
}>;
|
|
247
|
+
}
|
|
248
|
+
|
|
175
249
|
/**
|
|
176
250
|
* pixi-effects public DSL types.
|
|
177
251
|
*
|
|
@@ -588,6 +662,8 @@ type MusicDrums = {
|
|
|
588
662
|
interface MusicOptions {
|
|
589
663
|
/** Beats per minute, or `[beat, bpm]` points for a tempo change (a ritardando: `[[0, 96], [28, 96], [32, 60]]`). */
|
|
590
664
|
bpm: number | Array<[number, number]>;
|
|
665
|
+
/** Beats in a bar (default 4; a waltz is 3). Only names the bars in `musicEvents()` and the `check` read-back; playback does not use it. */
|
|
666
|
+
meter?: number;
|
|
591
667
|
tracks?: MusicTrack[];
|
|
592
668
|
/** Drum patterns that repeat, or a list of them with `from` / `to` (beats) for sections. */
|
|
593
669
|
drums?: MusicDrums | MusicDrums[];
|
|
@@ -853,6 +929,8 @@ interface AudioDescriptor {
|
|
|
853
929
|
layer?: string;
|
|
854
930
|
/** What it plays: `sfx "pop"`, `asset "bgm"`, `video "green"`. */
|
|
855
931
|
source?: string;
|
|
932
|
+
/** A music layer's score read back: bars per track, what starts in each bar, the drum spans (for `movie.inspectAudio()` and the `check` tool). */
|
|
933
|
+
music?: MusicSummary;
|
|
856
934
|
loop: boolean;
|
|
857
935
|
start: number;
|
|
858
936
|
end: number;
|
|
@@ -870,4 +948,4 @@ interface AudioDescriptor {
|
|
|
870
948
|
sourceMap?: (u: number) => number;
|
|
871
949
|
}
|
|
872
950
|
|
|
873
|
-
export { type
|
|
951
|
+
export { type SfxPreset as $, type ArcShapeSpec as A, type BlendModeName as B, type CompositionShape as C, type DipTransition as D, type EllipseShapeSpec as E, type FilterSpec as F, type GradientSpec as G, type MusicInstrument as H, type ImageSequenceSpec as I, type MusicLevel as J, type Keyframe as K, type LightSequenceSpec as L, type MusicOptions as M, type MusicNote as N, type MusicSummary as O, type Props as P, type MusicTrack as Q, type MusicTrackEvents as R, type SequenceSpec as S, type TextSequenceSpec as T, type NullSequenceSpec as U, type PathShapeSpec as V, type PolygonShapeSpec as W, type PropValue as X, type RectShapeSpec as Y, type SfxKnobs as Z, type SfxOptions as _, type CameraSequenceSpec as a, type SfxVoice as a0, type ShaderSequenceSpec as a1, type ShapeSequenceSpec as a2, type SlideTransition as a3, type Stop as a4, type StopSpec as a5, type TransitionCommon as a6, type VideoSequenceSpec as a7, type WipeTransition as a8, type ZoomTransition as a9, normalizeStops as aa, pictureStops as ab, type Timeline as ac, type AudioDescriptor as ad, type MusicEvents as b, type TransitionSpec as c, type CompositionSpec as d, type SequenceCommon as e, type AssetSpec as f, type AudioAssetSpec as g, type AudioMusicSpec as h, type AudioSequenceSpec as i, type AudioSfxSpec as j, type ChromaKeyFilterSpec as k, type CircleShapeSpec as l, type CompositionSequenceSpec as m, type CrossfadeTransition as n, type CustomFilterSpec as o, type DissolveTransition as p, type Expr as q, type IrisTransition as r, type LineShapeSpec as s, type LumaTransition as t, type MaskRefSpec as u, type MatteRefSpec as v, type MusicDrum as w, type MusicDrumEvents as x, type MusicDrumHit as y, type MusicDrums as z };
|
package/docs/api.md
CHANGED
|
@@ -211,6 +211,23 @@ The whole movie as rows on a time axis, for a person to read at a glance. `timel
|
|
|
211
211
|
|
|
212
212
|
`movie.timelineSvg()` is the chart alone (one `<svg>`, geometry in `data-*` attributes, each row `<g class="row" data-start>`): `pixi-effects-view` puts a playhead on it and turns a click into a seek.
|
|
213
213
|
|
|
214
|
+
### Your own layer type: `Sequence` and `registerSequenceType(type, ctor)`
|
|
215
|
+
|
|
216
|
+
A layer type that the library does not have (a different renderer, a data view) is a subclass of `Sequence`, registered under a `type` name; then `{ type: 'mine', at, duration, initial, keyframes, filters, mask }` works like any layer, in any composition.
|
|
217
|
+
|
|
218
|
+
```js
|
|
219
|
+
import { Sequence, registerSequenceType } from 'pixi-effects';
|
|
220
|
+
import { Container } from 'pixi.js';
|
|
221
|
+
|
|
222
|
+
class Mine extends Sequence {
|
|
223
|
+
async build() { this.target = new Container(); this.intrinsicWidth = 200; this.intrinsicHeight = 100; } // the display object the keyframes move
|
|
224
|
+
async awaitFrameAt(local) { /* optional: draw the frame at `local` seconds (playback and export both call it, so draw from the time only) */ }
|
|
225
|
+
}
|
|
226
|
+
registerSequenceType('mine', Mine);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`target` is the Pixi display object; `spec`, `parent`, `root`, `at`, `duration` are set for you; `scope()` gives the expression scope. Keyframes, filters, masks and the lifespan work on `target` as for the built-in layers. `awaitFrameAt(local)` is called once per frame by `gotoFrame` (the one path playback and export share), after the timeline has been set to that frame.
|
|
230
|
+
|
|
214
231
|
### `ease(name): (p: number) => number`
|
|
215
232
|
|
|
216
233
|
The function behind an ease name (`ease('power2.out')(0.5)`, `ease('spring.bouncy')`, `ease('cubic-bezier(.4, 0, .2, 1)')`), for a preset or a check of your own. A name nobody knows warns once and runs as `power1.out`.
|
|
@@ -351,6 +368,17 @@ const env = await musicEnvelope(music, { frameRate: 30 });
|
|
|
351
368
|
|
|
352
369
|
The [audio-reactive](#audio-reactive-audioenvelope-react-bpmenvelope) envelope of an audio layer's [`music`](dsl.md#music-written-as-text-music): it renders the score and analyses that very sound, so `env.series` (`level`, `bass`, `mid`, `treble`, or your `bands`) follows the notes. `env.beats` are exact (the kick hits, or every beat when the score has no kick) and `env.bpm` is the starting tempo. Options are `audioEnvelope`'s (`frameRate`, `bands`, `beatBand`, `beatSensitivity`). Throws, saying why, when the score cannot play (for example no `bpm`). The envelope covers the music and its tail; when the layer starts at `at`, pass `at` to `react()`.
|
|
353
370
|
|
|
371
|
+
### `musicEvents()`
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
import { musicEvents } from 'pixi-effects';
|
|
375
|
+
const ev = await musicEvents(music); // the same object the audio layer plays; BEFORE movie.init, async like musicEnvelope()
|
|
376
|
+
ev.tracks[0].notes[0]; // { beat: 0, time: 0, duration: 4, seconds: 2, bar: 1, beatInBar: 1, name: 'Am7', chord: true, midi: [57, 60, 64, 67], velocity: 0.8 }
|
|
377
|
+
ev.drums[0]; // { kind: 'kick', hits: [{ beat, time, bar, beatInBar, velocity }, ...] }
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
What a [`music`](dsl.md#music-written-as-text-music) score plays, as data: every note and chord with its `beat` and `time` (seconds from the start of the music, the tempo map applied: add the audio layer's `at` for movie time; swing and humanize are not applied), `duration`, `bar` and `beatInBar` (from `meter`, default 4), the audible `midi` pitches (`transpose` is in), the `name` **as the score wrote it** (a transposed score still says `Am7` while `midi` sounds a tone higher: caption from `midi` if you transpose), and every drum hit with the pattern repeated out. Also `bpm`, `meter`, `beats`, `bars`, `seconds` and per track `beats` / `bars`. Use it to make the picture follow the score: light the keys of the chord that sounds, caption its name, flash on the snare. A score that cannot play throws with the reason; warnings are logged as for the layer.
|
|
381
|
+
|
|
354
382
|
### `musicBuffer()`
|
|
355
383
|
|
|
356
384
|
```ts
|
package/docs/dsl.md
CHANGED
|
@@ -254,9 +254,9 @@ A tune with no audio file: notes, chords and drum patterns written as strings, p
|
|
|
254
254
|
} }
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
**Layer.** `at` starts it, `volume` (and volume keyframes) scale it. `duration` defaults to the music's length plus its reverb tail (`tail`, 2.5 s), but never past the end of the composition (the music then ends with the movie, with a short fade). A shorter `duration` cuts it with a fade; a longer one is silent after the music unless **`loop: true`**, which repeats the score until the layer ends (default: the end of the composition). At `volume: 1` the music peaks at −6 dBFS (about −20 dB RMS), a bed that sits under speech and sound effects (an sfx peaks at −12 dBFS). Measured: a 4-bar tune (bell + bass + kick, a pluck and a hat on the repeat) at `volume: 0.8` reads −22 LUFS and −7.7 dBTP (`piano-roll`); web video is usually −14 to −16 LUFS, so 0.8 is not a limit: raise `volume` and watch `inspectAudio().loudness.truePeakDb`. `inspectAudio()` lists a music layer as ONE source (its start); to line a picture up with a note,
|
|
257
|
+
**Layer.** `at` starts it, `volume` (and volume keyframes) scale it. `duration` defaults to the music's length plus its reverb tail (`tail`, 2.5 s), but never past the end of the composition (the music then ends with the movie, with a short fade). A shorter `duration` cuts it with a fade; a longer one is silent after the music unless **`loop: true`**, which repeats the score until the layer ends (default: the end of the composition). At `volume: 1` the music peaks at −6 dBFS (about −20 dB RMS), a bed that sits under speech and sound effects (an sfx peaks at −12 dBFS). Measured: a 4-bar tune (bell + bass + kick, a pluck and a hat on the repeat) at `volume: 0.8` reads −22 LUFS and −7.7 dBTP (`piano-roll`); web video is usually −14 to −16 LUFS, so 0.8 is not a limit: raise `volume` and watch `inspectAudio().loudness.truePeakDb`. `inspectAudio()` lists a music layer as ONE source (its start); to line a picture up with a note, take its time from [`musicEvents(music)`](api.md#musicevents) (seconds from the start of the music, the tempo map applied: add the layer's `at`), or build the bars and the score from one array so they cannot drift.
|
|
258
258
|
|
|
259
|
-
**Score** (`music`): `bpm` (a number, or `[beat, bpm]` points for a tempo change: `[[0, 96], [28, 96], [32, 60]]` slows down over beats 28–32, a ritardando), `tracks`, `drums`, `swing` (0–0.4: delays the off-beat eighths and sixteenths; 0.16 is a gentle lo-fi lilt), `reverb` (0–0.6, default 0.22), `humanize` (seconds of seeded timing jitter, default 0.012), `drumVol` (default 0.8), `grid` (drum steps per beat, default 4), `transpose` (semitones, all pitched tracks), `tail`, `seed`. Misspelt names warn with "did you mean". The music's length is its longest track (drums repeat up to it), so a track of rests (`'_:16'`) sets a length.
|
|
259
|
+
**Score** (`music`): `bpm` (a number, or `[beat, bpm]` points for a tempo change: `[[0, 96], [28, 96], [32, 60]]` slows down over beats 28–32, a ritardando), `tracks`, `drums`, `swing` (0–0.4: delays the off-beat eighths and sixteenths; 0.16 is a gentle lo-fi lilt), `reverb` (0–0.6, default 0.22), `humanize` (seconds of seeded timing jitter, default 0.012), `drumVol` (default 0.8), `grid` (drum steps per beat, default 4), `meter` (beats in a bar, default 4: it only names the bars in [`musicEvents()`](api.md#musicevents) and in the `check` read-back, playback does not use it), `transpose` (semitones, all pitched tracks), `tail`, `seed`. Misspelt names warn with "did you mean". The music's length is its longest track (drums repeat up to it), so a track of rests (`'_:16'`) sets a length.
|
|
260
260
|
|
|
261
261
|
**Tracks.** `inst` is one of `keys` (electric piano), `pluck` (guitar / harp), `pad` (slow warm synth pad), `bass` (synth bass), `sub` (pure sine bass, for a heartbeat), `lead` (soft melodic lead), `bell` (glockenspiel), `musicbox` (a bright tine with a short ring). Options: `vol` (0–1.5, default 0.8, or `[beat, level]` points joined by lines for a fade-in or a swell), `pan` (−1…1), `step` (length of a token without `:`, in beats, default 1), `strum` (seconds between the notes of a chord, 0.01 sounds natural), `legato` (0.1–1, how much of a note's length it sounds), `tone` (0–1, brightness; lower is darker, a lo-fi dulling), `reverb` (0–1, this track's share of the reverb: dry bass, wet bells), `attack` / `release` (seconds; a pad's fade in and out), `ring` (seconds a bell or musicbox rings), `transpose` (semitones for this track). Good ranges: bass c1–c3, keys / pluck c3–c6, pad c2–c5, lead c4–c6, bell c5–c7.
|
|
262
262
|
|
|
@@ -271,13 +271,17 @@ A tune with no audio file: notes, chords and drum patterns written as strings, p
|
|
|
271
271
|
| `~` `~:1` | hold: lengthens the note before |
|
|
272
272
|
| `!` `,` | accent / soft, before or after the length: `c4!:1`, `c4:1,` |
|
|
273
273
|
|
|
274
|
-
**Drums.** `drums` is an object of patterns, or a list of them with `from` / `to` (beats) so a groove can start late or stop (`[{ kick: 'x...' }, { from: 8, snare: '....x...' }]`). A pattern is a string of steps: `x` hit, `o` soft hit, `.` nothing (`|` and spaces are ignored); one step is 1/`grid` of a beat, so with the default grid 4 a 16-step string is one bar of 4/4, and a longer string (32 steps) is a longer figure that repeats. Drums: `kick snare hat openhat clap rim tom crash shaker sleigh` (`sleigh` is a jingle of small bells).
|
|
274
|
+
**Drums.** `drums` is an object of patterns, or a list of them with `from` / `to` (beats) so a groove can start late or stop (`[{ kick: 'x...' }, { from: 8, snare: '....x...' }]`). A pattern is a string of steps: `x` hit, `o` soft hit, `.` nothing (`|` and spaces are ignored); one step is 1/`grid` of a beat, so with the default grid 4 a 16-step string is one bar of 4/4, and a longer string (32 steps) is a longer figure that repeats. **The pattern repeats by itself** from `from` to `to`: you write one bar, not eight. `from` and `to` are **beats** from the start of the music (not bars): bar 9 of a 4/4 piece is `from: 32`; leave `to` out to play to the end. Drums: `kick snare hat openhat clap rim tom crash shaker sleigh` (`sleigh` is a jingle of small bells).
|
|
275
275
|
|
|
276
276
|
**Other meters.** The notation counts beats (the quarter note is 1), so 3/4 is bars of 3 beats. 6/8 is bars of 3 beats too: a dotted quarter is `:1.5`, an eighth `:0.5`; set `grid: 2` so a drum step is an eighth note (a 6-step pattern is one bar; the same `grid: 2` gives 6 eighth steps per bar in 3/4), and leave `swing` at 0. `grid: 3` is for triplet eighths in 4/4 (a shuffle).
|
|
277
277
|
|
|
278
|
+
**Comping on named beats, and swing.** There is no "on beats 2 and 4" option: write the rests, `_:1 Dm7:0.5 _:0.5 _:1 Dm7:0.5 _:0.5` is a short chord on beats 2 and 4 of one bar. `swing` delays only notes that start on an off-beat eighth (`.5`) or sixteenth, so notes on whole beats stay straight; for a swung comp put the chords on the "and" (`_:1.5 Dm7:0.5 ...`).
|
|
279
|
+
|
|
280
|
+
**Read it back.** `check` prints a `music` block for every music layer: its length in bars per track (a track that ends early, or a bar that is short, is named), the names that start in each bar with their beat (`Am(2)`: on beat 2; no mark: on the downbeat), and where the drums play. That is how to verify a score without listening. Set `meter` for anything but 4/4, or a waltz is reported as bars of 4. [`musicEvents(music)`](api.md#musicevents) gives the same facts as data, with every note's beat, time and pitches.
|
|
281
|
+
|
|
278
282
|
**Hear it without a movie.** [`musicBuffer(music)`](api.md#musicbuffer) returns a Web Audio buffer; [`examples/music-lab.html`](../examples/music-lab.html) plays eight finished scores and shows each one as source.
|
|
279
283
|
|
|
280
|
-
**Seeing the music in the picture.** [`musicEnvelope(music, { frameRate })`](api.md#musicenvelope) analyses the same score into `level` / `bass` / `mid` / `treble` and exact beats for [`react()`](#react), so a title can pulse with the kick of a tune that has no file.
|
|
284
|
+
**Seeing the music in the picture.** [`musicEnvelope(music, { frameRate })`](api.md#musicenvelope) analyses the same score into `level` / `bass` / `mid` / `treble` and exact beats for [`react()`](#react), so a title can pulse with the kick of a tune that has no file. [`musicEvents(music)`](api.md#musicevents) lists the notes themselves (which chord, which pitches, which bar), so a keyboard can light the keys that sound or a caption can name the chord.
|
|
281
285
|
|
|
282
286
|
### `composition`
|
|
283
287
|
|
package/llms-full.txt
CHANGED
|
@@ -6,7 +6,7 @@ Concatenation of the skill, cheatsheet, pitfalls, recipes and the DSL / API refe
|
|
|
6
6
|
|
|
7
7
|
<!-- ===== skills/pixi-effects/SKILL.md ===== -->
|
|
8
8
|
|
|
9
|
-
> Written for pixi-effects 0.
|
|
9
|
+
> Written for pixi-effects 0.28.0. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
|
|
10
10
|
|
|
11
11
|
# pixi-effects — write a video as data
|
|
12
12
|
|
|
@@ -123,7 +123,7 @@ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate t
|
|
|
123
123
|
|
|
124
124
|
**audio** — a file: `asset`, `loop`, `volume`, `speed` (the pitch follows; `time` keyframes work as for video; not for `sfx` / `music`: change an sfx's `pitch` instead, or put it in a composition and remap that); `volume` (top level, or in `initial` like every other layer's starting values); fade with volume keyframes (`{ at: -2, to: { volume: 0 }, duration: 2 }` fades the last 2 s). Omit `duration` for a one-shot clip: it lasts exactly as long as the clip. With `loop: true` and no `duration` it lasts until the composition ends. An explicit `duration` longer than the clip goes silent after the clip unless `loop: true`.
|
|
125
125
|
**Or a sound effect with no file — `sfx`:** `{ type: 'audio', sfx: 'swoosh', at: 2 }` is the whole thing. Presets, default length in s: `click` 0.04 · `pop` 0.12 · `swoosh` 0.5 (loudest 0.16 s in, moves left → right) · `swipe` 0.22 · `hit` 0.7 · `riser` 1 (loudest at its END: `at = hit − duration`) · `chime` 1.4 · `beep` 0.16 · `coin` 0.4 · `glitch` 0.35 · `typewriter` 0.06. Knobs: `sfx: { preset, pitch /* semitones ±24 */, brightness /* −1 dark … 1 bright */, seed /* another take */ }`. **The layer's `duration` IS the sound's length** (omit it; set it to stretch, e.g. a riser; at most 10 s); `volume` is its level (1 = the preset's standard level, peaking at −12 … −18 dBFS by preset; above 1 is allowed: a sparse track of clicks measured −32 LUFS at 0.5 and needed about 2 to reach −24 LUFS). No `loop`: one layer per hit (a JS loop). Custom sound: `sfx: { voices: [{ wave: 'noise' | 'sine' | 'square' | 'saw' | 'triangle', freq: 440 | [from, to], filter: { type: 'lowpass' | 'highpass' | 'bandpass', freq: [from, to] }, envelope: 'fall' | 'bell' | 'swell' | 'hold', pan: [-1, 1] }] }`. The export (mp4 / webm / mov) encodes this same mix (lossy AAC / Opus), on time within a few ms.
|
|
126
|
-
**Or music with no file — `music`:** `{ type: 'audio', music: { bpm: 96, tracks: [{ inst: 'keys', notes: 'Cmaj7:4 Am7:4 Dm7:4 G7:4' }, { inst: 'bass', notes: 'c2:2 g1:2 …' }], drums: { kick: 'x...x...', snare: '....x...', hat: 'x.x.x.x.' } } }` — a tune written as text (notes in LOWERCASE `c4:2` in beats, chords start with a CAPITAL `Am7` `G7` `C@4` `Am7/e` (`g7` is a very high note, not G7), `[c4 e4 g4]`, rest `_`, hold `~`, `!` accent; `|` ignored). Instruments `keys pluck pad bass sub lead bell musicbox`; drums `kick snare hat openhat clap rim tom crash shaker sleigh` as 16th-note step strings (`x` hit, `o` soft, `.` none; a list of `{ from, to, … }` for sections). Score options `bpm` (or `[[beat, bpm], …]` for a ritardando) `swing reverb (0–0.6) humanize drumVol grid transpose tail seed`; track options `vol` (or `[[beat, level], …]` for a fade) `pan step strum legato tone reverb attack release ring transpose`. Lasts its notes + tail, ends with the movie, `loop: true` repeats; peaks −6 dBFS at `volume: 1`. `await musicEnvelope(music, { frameRate })` → an envelope for `react()`; `await musicBuffer(music)` → a Web Audio `AudioBuffer` to play yourself.
|
|
126
|
+
**Or music with no file — `music`:** `{ type: 'audio', music: { bpm: 96, tracks: [{ inst: 'keys', notes: 'Cmaj7:4 Am7:4 Dm7:4 G7:4' }, { inst: 'bass', notes: 'c2:2 g1:2 …' }], drums: { kick: 'x...x...', snare: '....x...', hat: 'x.x.x.x.' } } }` — a tune written as text (notes in LOWERCASE `c4:2` in beats, chords start with a CAPITAL `Am7` `G7` `C@4` `Am7/e` (`g7` is a very high note, not G7), `[c4 e4 g4]`, rest `_`, hold `~`, `!` accent; `|` ignored). Instruments `keys pluck pad bass sub lead bell musicbox`; drums `kick snare hat openhat clap rim tom crash shaker sleigh` as 16th-note step strings (`x` hit, `o` soft, `.` none; a list of `{ from, to, … }` for sections). Score options `bpm` (or `[[beat, bpm], …]` for a ritardando) `meter` (beats in a bar, default 4: names the bars in `check`'s read-back, no effect on the sound) `swing reverb (0–0.6) humanize drumVol grid transpose tail seed`; track options `vol` (or `[[beat, level], …]` for a fade) `pan step strum legato tone reverb attack release ring transpose`. A plain capital letter is a major chord (`G`, `Bb`, `F#`; `Ebmaj7`, `F#m7`), `E5` is root and fifth only (e3 b3), every chord token is struck again (`~` holds), a track can start late with a rest (`_:32 e4:1 …`), `f#3` / `bb2` are sharps and flats, c4 is middle C, chord names are voiced around octave 3 (good ranges for notes: bass c1–c3, keys / pluck c3–c6, pad c2–c5, lead c4–c6, bell c5–c7). **Drums repeat by themselves** (one bar is enough) and `drums: { from: 32, to: 64, … }` counts BEATS, not bars (`drums` is one object, or a list of them for sections). `check` reads the score back (bars per track, what starts in each bar and on which beat: `Am(2)`; drum span): trust that over counting by hand; `await musicEvents(music)` gives the same as data. Lasts its notes + tail, ends with the movie, `loop: true` repeats; peaks −6 dBFS at `volume: 1`. `await musicEnvelope(music, { frameRate })` → an envelope for `react()`; `await musicBuffer(music)` → a Web Audio `AudioBuffer` to play yourself.
|
|
127
127
|
|
|
128
128
|
**shape** — `shape: 'rect' | 'circle' | 'ellipse' | 'arc' | 'line' | 'polygon' | 'path'`:
|
|
129
129
|
|
|
@@ -379,6 +379,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
379
379
|
97. **A deck inside your own composition loses its `transitions` and `stops`** unless you take them across: `const d = deck({ width: 1920, height: 1080, pages })`, then `{ type: 'composition', sequences: d.composition.sequences, transitions: d.composition.transitions }`. Pass `width` / `height` yourself for a page that is not the movie's own size.
|
|
380
380
|
98. **A matte layer needs a fill.** A named matte (`mask: 'glass'`) that is a shape with no `fillColor` / `fillGradient` (or a text with no fill) cuts everything away and nothing warns: the layers it should show simply vanish. Give the matte a fill (white for `alpha`, a gradient for `luma`).
|
|
381
381
|
99. **In a `music` score a capital letter is a chord and a lowercase one a note.** `G7` is G dominant seventh, `C5` a power chord, `C6` a sixth chord; `g7`, `c5`, `c6` are notes (octave 7 is a very high note: it used to be what `G7` meant, silently). A bare note in capitals (`Bb2`) is refused with the lowercase spelling (`bb2`); inside brackets (`[C4 E4 G4]`) any case is a note. An unknown chord kind suggests the nearest (`Cmin7` → `Cm7`, `Csus` → `Csus4`, `CM7` → `Cmaj7`).
|
|
382
|
+
100. **Read the score back before you trust it.** `check` prints a `music` block: every track's length in bars (a track that ends early or has a short bar is named), the names that start in each bar with their beat (`Am(2)` starts on beat 2; no mark = the downbeat; the first four names of a bar are shown, `musicEvents()` lists everything), and the drum span. Count bars by the read-back, not by hand: a bar of 3 beats in a score with no `meter` is shown as bars of 4, so set `meter: 3` for a waltz. Drums repeat by themselves and `from` / `to` are beats; `swing` only moves notes that start off the beat, so a comp on beats 2 and 4 stays straight.
|
|
382
383
|
|
|
383
384
|
|
|
384
385
|
<!-- ===== skills/pixi-effects/reference/recipes.md ===== -->
|
|
@@ -1775,9 +1776,9 @@ A tune with no audio file: notes, chords and drum patterns written as strings, p
|
|
|
1775
1776
|
} }
|
|
1776
1777
|
```
|
|
1777
1778
|
|
|
1778
|
-
**Layer.** `at` starts it, `volume` (and volume keyframes) scale it. `duration` defaults to the music's length plus its reverb tail (`tail`, 2.5 s), but never past the end of the composition (the music then ends with the movie, with a short fade). A shorter `duration` cuts it with a fade; a longer one is silent after the music unless **`loop: true`**, which repeats the score until the layer ends (default: the end of the composition). At `volume: 1` the music peaks at −6 dBFS (about −20 dB RMS), a bed that sits under speech and sound effects (an sfx peaks at −12 dBFS). Measured: a 4-bar tune (bell + bass + kick, a pluck and a hat on the repeat) at `volume: 0.8` reads −22 LUFS and −7.7 dBTP (`piano-roll`); web video is usually −14 to −16 LUFS, so 0.8 is not a limit: raise `volume` and watch `inspectAudio().loudness.truePeakDb`. `inspectAudio()` lists a music layer as ONE source (its start); to line a picture up with a note,
|
|
1779
|
+
**Layer.** `at` starts it, `volume` (and volume keyframes) scale it. `duration` defaults to the music's length plus its reverb tail (`tail`, 2.5 s), but never past the end of the composition (the music then ends with the movie, with a short fade). A shorter `duration` cuts it with a fade; a longer one is silent after the music unless **`loop: true`**, which repeats the score until the layer ends (default: the end of the composition). At `volume: 1` the music peaks at −6 dBFS (about −20 dB RMS), a bed that sits under speech and sound effects (an sfx peaks at −12 dBFS). Measured: a 4-bar tune (bell + bass + kick, a pluck and a hat on the repeat) at `volume: 0.8` reads −22 LUFS and −7.7 dBTP (`piano-roll`); web video is usually −14 to −16 LUFS, so 0.8 is not a limit: raise `volume` and watch `inspectAudio().loudness.truePeakDb`. `inspectAudio()` lists a music layer as ONE source (its start); to line a picture up with a note, take its time from [`musicEvents(music)`](api.md#musicevents) (seconds from the start of the music, the tempo map applied: add the layer's `at`), or build the bars and the score from one array so they cannot drift.
|
|
1779
1780
|
|
|
1780
|
-
**Score** (`music`): `bpm` (a number, or `[beat, bpm]` points for a tempo change: `[[0, 96], [28, 96], [32, 60]]` slows down over beats 28–32, a ritardando), `tracks`, `drums`, `swing` (0–0.4: delays the off-beat eighths and sixteenths; 0.16 is a gentle lo-fi lilt), `reverb` (0–0.6, default 0.22), `humanize` (seconds of seeded timing jitter, default 0.012), `drumVol` (default 0.8), `grid` (drum steps per beat, default 4), `transpose` (semitones, all pitched tracks), `tail`, `seed`. Misspelt names warn with "did you mean". The music's length is its longest track (drums repeat up to it), so a track of rests (`'_:16'`) sets a length.
|
|
1781
|
+
**Score** (`music`): `bpm` (a number, or `[beat, bpm]` points for a tempo change: `[[0, 96], [28, 96], [32, 60]]` slows down over beats 28–32, a ritardando), `tracks`, `drums`, `swing` (0–0.4: delays the off-beat eighths and sixteenths; 0.16 is a gentle lo-fi lilt), `reverb` (0–0.6, default 0.22), `humanize` (seconds of seeded timing jitter, default 0.012), `drumVol` (default 0.8), `grid` (drum steps per beat, default 4), `meter` (beats in a bar, default 4: it only names the bars in [`musicEvents()`](api.md#musicevents) and in the `check` read-back, playback does not use it), `transpose` (semitones, all pitched tracks), `tail`, `seed`. Misspelt names warn with "did you mean". The music's length is its longest track (drums repeat up to it), so a track of rests (`'_:16'`) sets a length.
|
|
1781
1782
|
|
|
1782
1783
|
**Tracks.** `inst` is one of `keys` (electric piano), `pluck` (guitar / harp), `pad` (slow warm synth pad), `bass` (synth bass), `sub` (pure sine bass, for a heartbeat), `lead` (soft melodic lead), `bell` (glockenspiel), `musicbox` (a bright tine with a short ring). Options: `vol` (0–1.5, default 0.8, or `[beat, level]` points joined by lines for a fade-in or a swell), `pan` (−1…1), `step` (length of a token without `:`, in beats, default 1), `strum` (seconds between the notes of a chord, 0.01 sounds natural), `legato` (0.1–1, how much of a note's length it sounds), `tone` (0–1, brightness; lower is darker, a lo-fi dulling), `reverb` (0–1, this track's share of the reverb: dry bass, wet bells), `attack` / `release` (seconds; a pad's fade in and out), `ring` (seconds a bell or musicbox rings), `transpose` (semitones for this track). Good ranges: bass c1–c3, keys / pluck c3–c6, pad c2–c5, lead c4–c6, bell c5–c7.
|
|
1783
1784
|
|
|
@@ -1792,13 +1793,17 @@ A tune with no audio file: notes, chords and drum patterns written as strings, p
|
|
|
1792
1793
|
| `~` `~:1` | hold: lengthens the note before |
|
|
1793
1794
|
| `!` `,` | accent / soft, before or after the length: `c4!:1`, `c4:1,` |
|
|
1794
1795
|
|
|
1795
|
-
**Drums.** `drums` is an object of patterns, or a list of them with `from` / `to` (beats) so a groove can start late or stop (`[{ kick: 'x...' }, { from: 8, snare: '....x...' }]`). A pattern is a string of steps: `x` hit, `o` soft hit, `.` nothing (`|` and spaces are ignored); one step is 1/`grid` of a beat, so with the default grid 4 a 16-step string is one bar of 4/4, and a longer string (32 steps) is a longer figure that repeats. Drums: `kick snare hat openhat clap rim tom crash shaker sleigh` (`sleigh` is a jingle of small bells).
|
|
1796
|
+
**Drums.** `drums` is an object of patterns, or a list of them with `from` / `to` (beats) so a groove can start late or stop (`[{ kick: 'x...' }, { from: 8, snare: '....x...' }]`). A pattern is a string of steps: `x` hit, `o` soft hit, `.` nothing (`|` and spaces are ignored); one step is 1/`grid` of a beat, so with the default grid 4 a 16-step string is one bar of 4/4, and a longer string (32 steps) is a longer figure that repeats. **The pattern repeats by itself** from `from` to `to`: you write one bar, not eight. `from` and `to` are **beats** from the start of the music (not bars): bar 9 of a 4/4 piece is `from: 32`; leave `to` out to play to the end. Drums: `kick snare hat openhat clap rim tom crash shaker sleigh` (`sleigh` is a jingle of small bells).
|
|
1796
1797
|
|
|
1797
1798
|
**Other meters.** The notation counts beats (the quarter note is 1), so 3/4 is bars of 3 beats. 6/8 is bars of 3 beats too: a dotted quarter is `:1.5`, an eighth `:0.5`; set `grid: 2` so a drum step is an eighth note (a 6-step pattern is one bar; the same `grid: 2` gives 6 eighth steps per bar in 3/4), and leave `swing` at 0. `grid: 3` is for triplet eighths in 4/4 (a shuffle).
|
|
1798
1799
|
|
|
1800
|
+
**Comping on named beats, and swing.** There is no "on beats 2 and 4" option: write the rests, `_:1 Dm7:0.5 _:0.5 _:1 Dm7:0.5 _:0.5` is a short chord on beats 2 and 4 of one bar. `swing` delays only notes that start on an off-beat eighth (`.5`) or sixteenth, so notes on whole beats stay straight; for a swung comp put the chords on the "and" (`_:1.5 Dm7:0.5 ...`).
|
|
1801
|
+
|
|
1802
|
+
**Read it back.** `check` prints a `music` block for every music layer: its length in bars per track (a track that ends early, or a bar that is short, is named), the names that start in each bar with their beat (`Am(2)`: on beat 2; no mark: on the downbeat), and where the drums play. That is how to verify a score without listening. Set `meter` for anything but 4/4, or a waltz is reported as bars of 4. [`musicEvents(music)`](api.md#musicevents) gives the same facts as data, with every note's beat, time and pitches.
|
|
1803
|
+
|
|
1799
1804
|
**Hear it without a movie.** [`musicBuffer(music)`](api.md#musicbuffer) returns a Web Audio buffer; [`examples/music-lab.html`](../examples/music-lab.html) plays eight finished scores and shows each one as source.
|
|
1800
1805
|
|
|
1801
|
-
**Seeing the music in the picture.** [`musicEnvelope(music, { frameRate })`](api.md#musicenvelope) analyses the same score into `level` / `bass` / `mid` / `treble` and exact beats for [`react()`](#react), so a title can pulse with the kick of a tune that has no file.
|
|
1806
|
+
**Seeing the music in the picture.** [`musicEnvelope(music, { frameRate })`](api.md#musicenvelope) analyses the same score into `level` / `bass` / `mid` / `treble` and exact beats for [`react()`](#react), so a title can pulse with the kick of a tune that has no file. [`musicEvents(music)`](api.md#musicevents) lists the notes themselves (which chord, which pitches, which bar), so a keyboard can light the keys that sound or a caption can name the chord.
|
|
1802
1807
|
|
|
1803
1808
|
### `composition`
|
|
1804
1809
|
|
|
@@ -3403,6 +3408,23 @@ The whole movie as rows on a time axis, for a person to read at a glance. `timel
|
|
|
3403
3408
|
|
|
3404
3409
|
`movie.timelineSvg()` is the chart alone (one `<svg>`, geometry in `data-*` attributes, each row `<g class="row" data-start>`): `pixi-effects-view` puts a playhead on it and turns a click into a seek.
|
|
3405
3410
|
|
|
3411
|
+
### Your own layer type: `Sequence` and `registerSequenceType(type, ctor)`
|
|
3412
|
+
|
|
3413
|
+
A layer type that the library does not have (a different renderer, a data view) is a subclass of `Sequence`, registered under a `type` name; then `{ type: 'mine', at, duration, initial, keyframes, filters, mask }` works like any layer, in any composition.
|
|
3414
|
+
|
|
3415
|
+
```js
|
|
3416
|
+
import { Sequence, registerSequenceType } from 'pixi-effects';
|
|
3417
|
+
import { Container } from 'pixi.js';
|
|
3418
|
+
|
|
3419
|
+
class Mine extends Sequence {
|
|
3420
|
+
async build() { this.target = new Container(); this.intrinsicWidth = 200; this.intrinsicHeight = 100; } // the display object the keyframes move
|
|
3421
|
+
async awaitFrameAt(local) { /* optional: draw the frame at `local` seconds (playback and export both call it, so draw from the time only) */ }
|
|
3422
|
+
}
|
|
3423
|
+
registerSequenceType('mine', Mine);
|
|
3424
|
+
```
|
|
3425
|
+
|
|
3426
|
+
`target` is the Pixi display object; `spec`, `parent`, `root`, `at`, `duration` are set for you; `scope()` gives the expression scope. Keyframes, filters, masks and the lifespan work on `target` as for the built-in layers. `awaitFrameAt(local)` is called once per frame by `gotoFrame` (the one path playback and export share), after the timeline has been set to that frame.
|
|
3427
|
+
|
|
3406
3428
|
### `ease(name): (p: number) => number`
|
|
3407
3429
|
|
|
3408
3430
|
The function behind an ease name (`ease('power2.out')(0.5)`, `ease('spring.bouncy')`, `ease('cubic-bezier(.4, 0, .2, 1)')`), for a preset or a check of your own. A name nobody knows warns once and runs as `power1.out`.
|
|
@@ -3543,6 +3565,17 @@ const env = await musicEnvelope(music, { frameRate: 30 });
|
|
|
3543
3565
|
|
|
3544
3566
|
The [audio-reactive](#audio-reactive-audioenvelope-react-bpmenvelope) envelope of an audio layer's [`music`](dsl.md#music-written-as-text-music): it renders the score and analyses that very sound, so `env.series` (`level`, `bass`, `mid`, `treble`, or your `bands`) follows the notes. `env.beats` are exact (the kick hits, or every beat when the score has no kick) and `env.bpm` is the starting tempo. Options are `audioEnvelope`'s (`frameRate`, `bands`, `beatBand`, `beatSensitivity`). Throws, saying why, when the score cannot play (for example no `bpm`). The envelope covers the music and its tail; when the layer starts at `at`, pass `at` to `react()`.
|
|
3545
3567
|
|
|
3568
|
+
### `musicEvents()`
|
|
3569
|
+
|
|
3570
|
+
```ts
|
|
3571
|
+
import { musicEvents } from 'pixi-effects';
|
|
3572
|
+
const ev = await musicEvents(music); // the same object the audio layer plays; BEFORE movie.init, async like musicEnvelope()
|
|
3573
|
+
ev.tracks[0].notes[0]; // { beat: 0, time: 0, duration: 4, seconds: 2, bar: 1, beatInBar: 1, name: 'Am7', chord: true, midi: [57, 60, 64, 67], velocity: 0.8 }
|
|
3574
|
+
ev.drums[0]; // { kind: 'kick', hits: [{ beat, time, bar, beatInBar, velocity }, ...] }
|
|
3575
|
+
```
|
|
3576
|
+
|
|
3577
|
+
What a [`music`](dsl.md#music-written-as-text-music) score plays, as data: every note and chord with its `beat` and `time` (seconds from the start of the music, the tempo map applied: add the audio layer's `at` for movie time; swing and humanize are not applied), `duration`, `bar` and `beatInBar` (from `meter`, default 4), the audible `midi` pitches (`transpose` is in), the `name` **as the score wrote it** (a transposed score still says `Am7` while `midi` sounds a tone higher: caption from `midi` if you transpose), and every drum hit with the pattern repeated out. Also `bpm`, `meter`, `beats`, `bars`, `seconds` and per track `beats` / `bars`. Use it to make the picture follow the score: light the keys of the chord that sounds, caption its name, flash on the snare. A score that cannot play throws with the reason; warnings are logged as for the layer.
|
|
3578
|
+
|
|
3546
3579
|
### `musicBuffer()`
|
|
3547
3580
|
|
|
3548
3581
|
```ts
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ name: pixi-effects
|
|
|
3
3
|
description: Use when asked to make, animate, preview or export a video, motion graphics, an animated title, a lower third, kinetic type, a promo, a slideshow with transitions, an animated chart or data story, a social clip, a 2.5D / depth scene, or music and sound for a video, written as code in JavaScript or HTML. pixi-effects is a JS library where a video is a plain-object composition (text, shape, image, video, audio and file-free sfx / music, camera, light and shader layers with keyframes) that plays in the browser, is checked by a tool the agent can run, and exports MP4 / WebM / MOV with no server. Not for a project that already uses another video tool (Remotion, Motion Canvas, ffmpeg); follow that tool instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> Written for pixi-effects 0.
|
|
6
|
+
> Written for pixi-effects 0.28.0. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
|
|
7
7
|
|
|
8
8
|
# pixi-effects — write a video as data
|
|
9
9
|
|
|
@@ -57,7 +57,7 @@ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate t
|
|
|
57
57
|
|
|
58
58
|
**audio** — a file: `asset`, `loop`, `volume`, `speed` (the pitch follows; `time` keyframes work as for video; not for `sfx` / `music`: change an sfx's `pitch` instead, or put it in a composition and remap that); `volume` (top level, or in `initial` like every other layer's starting values); fade with volume keyframes (`{ at: -2, to: { volume: 0 }, duration: 2 }` fades the last 2 s). Omit `duration` for a one-shot clip: it lasts exactly as long as the clip. With `loop: true` and no `duration` it lasts until the composition ends. An explicit `duration` longer than the clip goes silent after the clip unless `loop: true`.
|
|
59
59
|
**Or a sound effect with no file — `sfx`:** `{ type: 'audio', sfx: 'swoosh', at: 2 }` is the whole thing. Presets, default length in s: `click` 0.04 · `pop` 0.12 · `swoosh` 0.5 (loudest 0.16 s in, moves left → right) · `swipe` 0.22 · `hit` 0.7 · `riser` 1 (loudest at its END: `at = hit − duration`) · `chime` 1.4 · `beep` 0.16 · `coin` 0.4 · `glitch` 0.35 · `typewriter` 0.06. Knobs: `sfx: { preset, pitch /* semitones ±24 */, brightness /* −1 dark … 1 bright */, seed /* another take */ }`. **The layer's `duration` IS the sound's length** (omit it; set it to stretch, e.g. a riser; at most 10 s); `volume` is its level (1 = the preset's standard level, peaking at −12 … −18 dBFS by preset; above 1 is allowed: a sparse track of clicks measured −32 LUFS at 0.5 and needed about 2 to reach −24 LUFS). No `loop`: one layer per hit (a JS loop). Custom sound: `sfx: { voices: [{ wave: 'noise' | 'sine' | 'square' | 'saw' | 'triangle', freq: 440 | [from, to], filter: { type: 'lowpass' | 'highpass' | 'bandpass', freq: [from, to] }, envelope: 'fall' | 'bell' | 'swell' | 'hold', pan: [-1, 1] }] }`. The export (mp4 / webm / mov) encodes this same mix (lossy AAC / Opus), on time within a few ms.
|
|
60
|
-
**Or music with no file — `music`:** `{ type: 'audio', music: { bpm: 96, tracks: [{ inst: 'keys', notes: 'Cmaj7:4 Am7:4 Dm7:4 G7:4' }, { inst: 'bass', notes: 'c2:2 g1:2 …' }], drums: { kick: 'x...x...', snare: '....x...', hat: 'x.x.x.x.' } } }` — a tune written as text (notes in LOWERCASE `c4:2` in beats, chords start with a CAPITAL `Am7` `G7` `C@4` `Am7/e` (`g7` is a very high note, not G7), `[c4 e4 g4]`, rest `_`, hold `~`, `!` accent; `|` ignored). Instruments `keys pluck pad bass sub lead bell musicbox`; drums `kick snare hat openhat clap rim tom crash shaker sleigh` as 16th-note step strings (`x` hit, `o` soft, `.` none; a list of `{ from, to, … }` for sections). Score options `bpm` (or `[[beat, bpm], …]` for a ritardando) `swing reverb (0–0.6) humanize drumVol grid transpose tail seed`; track options `vol` (or `[[beat, level], …]` for a fade) `pan step strum legato tone reverb attack release ring transpose`. Lasts its notes + tail, ends with the movie, `loop: true` repeats; peaks −6 dBFS at `volume: 1`. `await musicEnvelope(music, { frameRate })` → an envelope for `react()`; `await musicBuffer(music)` → a Web Audio `AudioBuffer` to play yourself.
|
|
60
|
+
**Or music with no file — `music`:** `{ type: 'audio', music: { bpm: 96, tracks: [{ inst: 'keys', notes: 'Cmaj7:4 Am7:4 Dm7:4 G7:4' }, { inst: 'bass', notes: 'c2:2 g1:2 …' }], drums: { kick: 'x...x...', snare: '....x...', hat: 'x.x.x.x.' } } }` — a tune written as text (notes in LOWERCASE `c4:2` in beats, chords start with a CAPITAL `Am7` `G7` `C@4` `Am7/e` (`g7` is a very high note, not G7), `[c4 e4 g4]`, rest `_`, hold `~`, `!` accent; `|` ignored). Instruments `keys pluck pad bass sub lead bell musicbox`; drums `kick snare hat openhat clap rim tom crash shaker sleigh` as 16th-note step strings (`x` hit, `o` soft, `.` none; a list of `{ from, to, … }` for sections). Score options `bpm` (or `[[beat, bpm], …]` for a ritardando) `meter` (beats in a bar, default 4: names the bars in `check`'s read-back, no effect on the sound) `swing reverb (0–0.6) humanize drumVol grid transpose tail seed`; track options `vol` (or `[[beat, level], …]` for a fade) `pan step strum legato tone reverb attack release ring transpose`. A plain capital letter is a major chord (`G`, `Bb`, `F#`; `Ebmaj7`, `F#m7`), `E5` is root and fifth only (e3 b3), every chord token is struck again (`~` holds), a track can start late with a rest (`_:32 e4:1 …`), `f#3` / `bb2` are sharps and flats, c4 is middle C, chord names are voiced around octave 3 (good ranges for notes: bass c1–c3, keys / pluck c3–c6, pad c2–c5, lead c4–c6, bell c5–c7). **Drums repeat by themselves** (one bar is enough) and `drums: { from: 32, to: 64, … }` counts BEATS, not bars (`drums` is one object, or a list of them for sections). `check` reads the score back (bars per track, what starts in each bar and on which beat: `Am(2)`; drum span): trust that over counting by hand; `await musicEvents(music)` gives the same as data. Lasts its notes + tail, ends with the movie, `loop: true` repeats; peaks −6 dBFS at `volume: 1`. `await musicEnvelope(music, { frameRate })` → an envelope for `react()`; `await musicBuffer(music)` → a Web Audio `AudioBuffer` to play yourself.
|
|
61
61
|
|
|
62
62
|
**shape** — `shape: 'rect' | 'circle' | 'ellipse' | 'arc' | 'line' | 'polygon' | 'path'`:
|
|
63
63
|
|
|
@@ -127,3 +127,4 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
127
127
|
97. **A deck inside your own composition loses its `transitions` and `stops`** unless you take them across: `const d = deck({ width: 1920, height: 1080, pages })`, then `{ type: 'composition', sequences: d.composition.sequences, transitions: d.composition.transitions }`. Pass `width` / `height` yourself for a page that is not the movie's own size.
|
|
128
128
|
98. **A matte layer needs a fill.** A named matte (`mask: 'glass'`) that is a shape with no `fillColor` / `fillGradient` (or a text with no fill) cuts everything away and nothing warns: the layers it should show simply vanish. Give the matte a fill (white for `alpha`, a gradient for `luma`).
|
|
129
129
|
99. **In a `music` score a capital letter is a chord and a lowercase one a note.** `G7` is G dominant seventh, `C5` a power chord, `C6` a sixth chord; `g7`, `c5`, `c6` are notes (octave 7 is a very high note: it used to be what `G7` meant, silently). A bare note in capitals (`Bb2`) is refused with the lowercase spelling (`bb2`); inside brackets (`[C4 E4 G4]`) any case is a note. An unknown chord kind suggests the nearest (`Cmin7` → `Cm7`, `Csus` → `Csus4`, `CM7` → `Cmaj7`).
|
|
130
|
+
100. **Read the score back before you trust it.** `check` prints a `music` block: every track's length in bars (a track that ends early or has a short bar is named), the names that start in each bar with their beat (`Am(2)` starts on beat 2; no mark = the downbeat; the first four names of a bar are shown, `musicEvents()` lists everything), and the drum span. Count bars by the read-back, not by hand: a bar of 3 beats in a score with no `meter` is shown as bars of 4, so set `meter: 3` for a waltz. Drums repeat by themselves and `from` / `to` are beats; `swing` only moves notes that start off the beat, so a comp on beats 2 and 4 stays straight.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>pixi-effects video</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.28.0/dist/loader.css" /> <!-- the loader: HTML + CSS only, so it is on screen at once and keeps moving while the page builds the video -->
|
|
8
8
|
<style>
|
|
9
9
|
body { background: #0d1220; margin: 0; padding: 16px; }
|
|
10
10
|
.stage { position: relative; width: min(960px, 100%); } /* the player and the loader both sit inside this box */
|
|
@@ -37,16 +37,16 @@
|
|
|
37
37
|
"imports": {
|
|
38
38
|
"pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
|
|
39
39
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
40
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
41
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
42
|
-
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
43
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
40
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.0/dist/index.js",
|
|
41
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.0/dist/Controller.js",
|
|
42
|
+
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.0/dist/Presenter.js",
|
|
43
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.28.0/dist/three.js",
|
|
44
44
|
"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js",
|
|
45
45
|
"three": "https://esm.sh/three@0.178.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
</script>
|
|
49
|
-
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
49
|
+
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.28.0/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
|
|
50
50
|
|
|
51
51
|
<script type="module">
|
|
52
52
|
import { Movie, kenBurns, withFade } from 'pixi-effects';
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkC7HJJ7MZ_cjs = require('./chunk-C7HJJ7MZ.cjs');
|
|
4
|
-
require('./chunk-YOV6MEXH.cjs');
|
|
5
|
-
require('./chunk-P4GY3Z4G.cjs');
|
|
6
|
-
require('./chunk-ZMAD3KJU.cjs');
|
|
7
|
-
require('./chunk-TWZNOUM7.cjs');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Object.defineProperty(exports, "CompositionSequence", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
get: function () { return chunkC7HJJ7MZ_cjs.CompositionSequence; }
|
|
14
|
-
});
|
|
15
|
-
//# sourceMappingURL=Composition-DRCTWIYH.cjs.map
|
|
16
|
-
//# sourceMappingURL=Composition-DRCTWIYH.cjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { CompositionSequence } from './chunk-K3F2YKL4.js';
|
|
2
|
-
import './chunk-BQNNZEAX.js';
|
|
3
|
-
import './chunk-TYWFKCPY.js';
|
|
4
|
-
import './chunk-45QKLD6M.js';
|
|
5
|
-
import './chunk-BXOCEZ4U.js';
|
|
6
|
-
//# sourceMappingURL=Composition-TZHVYIGH.js.map
|
|
7
|
-
//# sourceMappingURL=Composition-TZHVYIGH.js.map
|