GraphicSpec Specification — Building Your Own "Background Effect"
This document is a translation of the Japanese original. The Japanese version is authoritative — if the two differ, the Japanese version takes precedence.
This document is the complete specification for GraphicSpec, movlyric's definition of background effects (shapes and lines drawn behind the lyrics). You use it from the app's Style Editor → "Background" tab → Background effect → "Custom" → "Create with AI…" (character motion — which animates the lyric text itself — is a separate thing; see ANIMATION_SPEC (Character Motion) for that spec). The goal is that an AI (or a human) who has read only this document can write JSON that the app accepts as-is. A prompt template for handing to an AI appears at the end of this document. You can also paste this entire document in as part of the prompt, and it will still work.
1. Overview
A background effect is motion graphics that move behind the lyrics (the motion of the lyrics themselves is handled by the separate AnimationSpec).
GraphicSpec is written as "arrange N copies of the same shape and animate them." There are four shape types: circle, rectangle, line, and polygon.
Each shape's position, size, color, and opacity can be defined in one of two ways:
- A standard composite (
base value + random + sine wave + drift + audio) — concise to write - A formula — free-form calculation, e.g.
0.5 + sin(t * 2 + i) * 0.2. This also lets you write conditional branches and chained placement that references the previous shape's position
Formulas are not JavaScript. You cannot write assignments, loops, or function definitions — a single expression just returns a single number (the same idea as a spreadsheet formula). Only the names listed in §2.6 can be used.
2. Complete JSON Format Specification
2.1 Root: GraphicSpec
{ "layers": [ <Layer>, ... ] }
| Field | Type | Required | Description |
|---|---|---|---|
layers |
Layer[] | Required | 1–4 items. Layers written earlier are drawn further back; later ones are drawn in front |
This is the only key that may appear directly under the root.
2.2 Layer
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
shape |
String | Required | "circle" / "rect" / "line" / "polygon" |
The shape type |
count |
Integer | Required | 1–200 | Number of shapes |
sides |
Integer | Optional | 3–24 | Number of vertices for a polygon (default 5). Ignored for other shapes |
star |
Number | Optional | 0–1 | How star-shaped the polygon is. 0 = regular polygon, 0.5 = a typical star. Ignored for other shapes |
when |
String | Optional | Formula | Draw condition. Any instance evaluating to 0 is not drawn (§2.6) |
spread |
String | Optional | §2.4 | Initial placement across the count of shapes. If omitted, all shapes overlap at the same position |
x |
Value | Required | — | Horizontal position, as a ratio of screen width (0 = left edge, 0.5 = center, 1 = right edge) |
y |
Value | Required | — | Vertical position, as a ratio of screen height (0 = top edge, 1 = bottom edge) |
size |
Value | Required | — | Size, as a ratio of screen height. circle = radius / rect = height / line = length |
aspect |
Number | Optional | 0.01–100 | rect's width = size × aspect. For line, this affects thickness. Ignored for circle |
rotation |
Value | Optional | — | Rotation, in degrees. Ignored for circle |
color |
String or Integer | Required | §2.5 | Color |
alpha |
Value | Required | — | Opacity. Clamped to 0–1 |
wrap |
Boolean | Optional | — | If true, shapes that go off-screen reappear from the opposite side (a continuous-flow effect) |
2.3 Value (how a value is determined)
There are three ways to write it.
| Form | Example | Use |
|---|---|---|
| Number | "x": 0.5 |
A constant |
| Formula (string) | "x": "0.5 + sin(t) * 0.2" |
Free-form calculation, conditional branches, dependence on the previous shape (§2.6) |
| Object | see below | A standard composite of "base value + random + wave + drift + audio" |
When in doubt, a formula is easier to write. The object form exists to concisely express common combinations.
Write a plain number for a constant ("x": 0.5). The object form looks like this:
{
"base": 0.5,
"random": [-0.2, 0.2],
"wave": { "amplitude": 0.05, "periodMs": 3000, "phaseByIndex": 0.7 },
"drift": -0.1,
"audio": { "band": 2, "scale": 0.3 }
}
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
base |
Number | Required | — | The base value |
random |
[Number, Number] | Optional | — | A random value within this range is added to base. Each shape gets a different value (deterministic randomness — the same picture every time) |
wave |
Object | Optional | — | Oscillates with a sine wave (see below) |
drift |
Number | Optional | — | A linear rate of change over time (per second). Negative for the opposite direction |
audio |
Object | Optional | — | Reacts to audio (see below) |
wave
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
amplitude |
Number | Required | — | The oscillation amount |
periodMs |
Integer | Required | 50–60000 | Time for one full cycle, in milliseconds. No decimals |
phaseByIndex |
Number | Optional | — | How much to offset the phase per shape. If omitted, every shape moves in lockstep, so use roughly 0.3–1.0 when you want them spread out |
audio
| Field | Type | Required | Range | Description |
|---|---|---|---|---|
band |
Integer | Required | 0–63 | The frequency band. 0 is the lowest pitch (kick), and higher numbers mean higher pitch |
scale |
Number | Required | — | The amount multiplied by the band's level (0–1) and added to base |
As a guideline, use band 0–3 for bass, 8–16 for mids, and 24 or higher for treble.
2.4 spread (initial placement)
This is added to the x/y value.
| Value | Placement |
|---|---|
"none" (default) |
All shapes at the same position. Used when you plan to scatter them with random |
"random" |
Scattered randomly across the whole screen (deterministic randomness — the same layout every time) |
"row" |
Evenly spaced in a horizontal row |
"column" |
Evenly spaced in a vertical column |
"ring" |
Evenly spaced around a circle |
"spiral" |
Spirals outward from the center (spaced by the golden angle to avoid overlap) |
"grid" |
A roughly square grid |
2.5 color
Referencing the palette is recommended. This way the effect updates along with the effect whenever the user changes the color scheme.
| Value | Meaning |
|---|---|
"text" |
The text color |
"accent" |
The accent color (the chorus color) |
"stroke" |
The stroke color |
| Integer | A direct 0xRRGGBB value (e.g., red = 16711680). The string form "#ff0000" is not allowed |
2.6 Formulas
Writing a string as a value makes it a formula. Up to 200 characters.
{ "x": "0.5 + sin(t * 2 + i * 0.5) * 0.2" }
Available variables
| Name | Meaning |
|---|---|
t |
Elapsed seconds |
at |
Elapsed seconds of audio (use this when reacting to audio) |
i |
This shape's index (starting from 0) |
n |
The total number of shapes |
p |
i/(n-1) (position normalized to 0–1; 0 when n=1) |
beat |
Beat progress 0–1 (0 at the start of a beat) |
energy |
The section's energy, 0–1 |
chorus |
1 while in the chorus, otherwise 0 |
aspect |
The screen's aspect ratio (width/height) |
px py psize prot |
The previous shape's position, size, and rotation (§2.7) |
PI TAU |
π and 2π |
Available functions
sin cos tan asin acos atan atan2 abs sqrt exp log sign floor ceil round min max pow hypot mod clamp lerp step smoothstep rand band
mod(a, b)— always returns a positive remainder (mod(-1, 3)= 2)clamp(v, min, max)/lerp(a, b, t)/step(edge, v)/smoothstep(e0, e1, v)rand(k)— a random value 0–1 unique to this shape (changingkgives a different series; deterministic — the same value every time)band(k)— the level of frequency bandk, 0–1 (0 is the lowest pitch)
Operators
+ - * / % ^ (exponentiation), comparisons < <= > >= == !=, logical && || !, and the ternary operator condition ? A : B.
Write conditional branches with the ternary operator.
{ "size": "chorus ? 0.15 : 0.05" }
What you cannot write
Assignment to variables, loops, function definitions, and strings are all disallowed. A single expression must produce a single number. Any name not in the tables above (Math, Date, random, etc.) will cause an error.
2.7 Calculations that depend on the previous shape
px / py / psize / prot are the resolved values of the previous shape (i-1). This lets you write chains and stacking effects. Values are passed through still as screen ratios.
{ "x": "px + 0.15", "y": "py + sin(i) * 0.05" }
For the first shape (i = 0), these start at 0. If you want to treat "just the first one" differently, branch on i.
{ "size": "i == 0 ? 0.1 : psize * 0.8" }
3. Common Mistakes (Prohibited)
- Out-of-range values:
count: 0or300,periodMs: 10or100000,band: 64,layerswith 5 or more items are all invalid - Nonexistent properties: only the fields in §2.2 may appear in a Layer, and only the 5 fields in §2.3 may appear in a Value object.
speed,opacity,radius,points, etc. do not exist - Writing a name that doesn't exist in a formula:
Math.sin(t)is not allowed (writesin(t)).Date,random,window, etc. cannot be used - Writing loops or assignments in a formula:
for,=,letcannot be written. A single expression produces a single number - Writing colors as strings:
"#ff0066"or"red"are not allowed. Use a palette name or an integer - Fields that must not be decimals:
count/periodMs/bandare integers only - Mixing up units: x/y/size are a ratio of the screen, not pixels.
"size": 40means "40 times the screen height" and fills the whole screen. Sizes around 0.01–0.2 are typical - Forgetting
phaseByIndex: without it, every shape moves in lockstep, which looks mechanical and unnatural - Anything that isn't JSON: comments and trailing commas are not allowed (formulas can be written as strings — see §2.6)
4. Examples
All of these are complete JSON that will pass as-is if pasted into the app.
4.1 Softly drifting particles of light
Randomly scattered circles slowly drift upward, swaying side to side while flickering. Once they pass the top of the screen, they reappear from the bottom.
{
"layers": [
{
"shape": "circle",
"count": 24,
"spread": "random",
"x": { "base": 0, "wave": { "amplitude": 0.02, "periodMs": 7000, "phaseByIndex": 0.9 } },
"y": { "base": 0, "drift": -0.05 },
"size": { "base": 0.03, "random": [0, 0.03] },
"color": "accent",
"alpha": { "base": 0.18, "wave": { "amplitude": 0.1, "periodMs": 4000, "phaseByIndex": 0.6 } },
"wrap": true
}
]
}
4.2 An audio-reactive equalizer
Vertical bars lined up at the bottom of the screen stretch and shrink with the level of each frequency band, from bass to treble.
{
"layers": [
{
"shape": "rect",
"count": 32,
"spread": "row",
"x": 0.5,
"y": 0.95,
"size": { "base": 0.02, "audio": { "band": 4, "scale": 0.5 } },
"aspect": 0.4,
"color": "accent",
"alpha": 0.75
}
]
}
4.3 Diagonal streaming lines
Diagonal lines slowly sweep across the background continuously. A thin, faint layer sits behind a bolder, denser layer to create depth.
{
"layers": [
{
"shape": "line",
"count": 14,
"spread": "random",
"x": { "base": 0, "drift": 0.06 },
"y": 0,
"size": { "base": 0.5, "random": [0, 0.4] },
"aspect": 0.03,
"rotation": -24,
"color": "text",
"alpha": 0.06,
"wrap": true
},
{
"shape": "line",
"count": 5,
"spread": "random",
"x": { "base": 0, "drift": 0.11 },
"y": 0,
"size": { "base": 0.7, "random": [0, 0.3] },
"aspect": 0.05,
"rotation": -24,
"color": "accent",
"alpha": 0.14,
"wrap": true
}
]
}
4.4 Rings pulsing on the kick
Points arranged around a ring expand outward in time with the bass (kick).
{
"layers": [
{
"shape": "circle",
"count": 24,
"spread": "ring",
"x": { "base": 0.5, "audio": { "band": 0, "scale": 0.06 } },
"y": { "base": 0.5, "audio": { "band": 0, "scale": 0.06 } },
"size": { "base": 0.012, "audio": { "band": 0, "scale": 0.02 } },
"color": "accent",
"alpha": 0.5
}
]
}
4.5 Stars that only appear during the chorus (conditional branch + polygon)
Star shapes appear and rotate only during the chorus. when toggles the whole layer on and off, and its size pulses with the beat.
{
"layers": [
{
"shape": "polygon",
"sides": 5,
"star": 0.55,
"count": 18,
"spread": "random",
"when": "chorus",
"x": 0,
"y": { "base": 0, "drift": -0.03 },
"size": "0.02 + (1 - beat) * 0.015 + rand(0) * 0.02",
"rotation": "t * 40 + i * 20",
"color": "accent",
"alpha": 0.5,
"wrap": true
}
]
}
4.6 Chained, extending lines (depends on the previous shape)
Starting from the first line, each subsequent line is chained near the endpoint of the previous one. This example uses px / py.
{
"layers": [
{
"shape": "line",
"count": 40,
"x": "i == 0 ? 0.05 : px + 0.022",
"y": "i == 0 ? 0.5 : py + sin(t + i * 0.4) * 0.012",
"size": 0.04,
"aspect": 0.06,
"rotation": "sin(t + i * 0.4) * 40",
"color": "accent",
"alpha": "0.15 + p * 0.5"
}
]
}
5. AI Prompt Template
Copy the block below and hand it to an AI. It's recommended to paste this entire specification first, and follow it immediately with the template (handing the template alone to an AI, without the spec, tends to produce mistakes in ranges or units).
Copy from here
You are a background effects designer for movlyric. Strictly following the "GraphicSpec Specification" pasted above, create a single GraphicSpec JSON.
Output rules:
- Output JSON only (no explanation, preamble, or closing remarks; wrapping the JSON in a code fence is fine)
- The root is {"layers": [...]}, with 1–4 layers
- The only fields allowed in a Layer are shape, count, sides, star, when, spread, x, y, size, aspect, rotation, color, alpha, wrap
- shape is one of "circle" / "rect" / "line" / "polygon". polygon can have sides(3..24) and star(0..1)
- count is an integer from 1 to 200
- x, y, size, and alpha are required. Each can be written in one of three ways:
(a) a plain number (constant)
(b) **a formula string** (up to 200 characters)
(c) {"base":..., "random":[min,max], "wave":{"amplitude":...,"periodMs":integer 50..60000,"phaseByIndex":...}, "drift":..., "audio":{"band":integer 0..63,"scale":...}}
- The only variables usable in a formula are t, at, i, n, p, beat, energy, chorus, aspect, px, py, psize, prot, PI, TAU
- The only functions usable in a formula are sin cos tan asin acos atan atan2 abs sqrt exp log sign floor ceil round min max pow hypot mod clamp lerp step smoothstep rand(k) band(k)
- Formulas cannot contain assignment, loops, function definitions, or strings. Write conditional branches with the ternary operator `condition ? A : B`
- **Writing Math.sin(t) is not allowed** (write sin(t)). Names like Date / random / window cannot be used
- To depend on the previous shape, use px / py / psize / prot (passed through as screen ratios; 0 when i=0)
- To toggle a whole layer on and off, put a formula in `when` (not drawn if it evaluates to 0)
- **x/y/size are ratios of the screen** (not pixels). size is typically around 0.01–0.2. x/y of 0–1 is on-screen
- color is one of "text" / "accent" / "stroke", or an integer in 0xRRGGBB form. String color codes are not allowed
- When animating multiple shapes, offset their phase with wave's phaseByIndex (without it, everything moves in lockstep and looks unnatural)
- To react to audio, use audio. band 0 is the lowest pitch (kick), and higher numbers mean higher pitch
- To keep shapes flowing off-screen continuously, combine drift with wrap:true
- Do not write nonexistent properties, comments, trailing commas, or expressions
Effect I want to create:
{describe the motion you want here}
(Only if you're modifying an existing effect, paste the current JSON below. For a new creation, delete this entire section.)
Current JSON:
{current JSON}
Copy up to here