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:

  1. A standard composite (base value + random + sine wave + drift + audio) — concise to write
  2. 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 (changing k gives a different series; deterministic — the same value every time)
  • band(k) — the level of frequency band k, 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: 0 or 300, periodMs: 10 or 100000, band: 64, layers with 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 (write sin(t)). Date, random, window, etc. cannot be used
  • Writing loops or assignments in a formula: for, =, let cannot 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 / band are integers only
  • Mixing up units: x/y/size are a ratio of the screen, not pixels. "size": 40 means "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