GraphicSpec 仕様書 — 自作の「背景エフェクト」

この文書は movlyric の背景エフェクト(歌詞の背後に図形・線を描く)の定義「GraphicSpec」の完全仕様です。 アプリの「スタイルエディタ →「背景」タブ → 背景エフェクト →「カスタム」→ AIで作る…」から使います(歌詞の文字そのものを動かす「文字の動き」は別物で、 そちらは ANIMATION_SPEC(文字の動き) が仕様です)。 この文書だけを読んだ AI(または人間)が、アプリにそのまま貼って通る JSON を書けることを目的とします。 文書末尾に AI へ渡すプロンプトテンプレートがあります。この文書全体をプロンプトの一部として貼り付けても機能します。

1. 概要

背景エフェクトは、歌詞の背後で動くモーショングラフィックです(歌詞そのものの動きは別仕様の AnimationSpec が扱います)。

GraphicSpec は「同じ形の図形を N 個並べて動かす」という形で書きます。図形は円・長方形・線・多角形の4種類です。

各図形の位置・大きさ・色・不透明度は、2通りの書き方で決められます。

  1. 定型の合成基準値 + 乱数 + 正弦波 + 流れ + 音)— 短く書ける
  2. 数式0.5 + sin(t * 2 + i) * 0.2 のように自由に計算できる。条件分岐や、直前の図形の位置を参照した連鎖配置も書ける

数式は JavaScript ではありません。代入・ループ・関数定義は書けず、1つの式が1つの数値を返すだけです(表計算の数式と同じ考え方)。使える名前は §2.6 の一覧に限られます。

2. JSON 形式の完全仕様

2.1 ルート: GraphicSpec

{ "layers": [ <Layer>, ... ] }
フィールド 必須 説明
layers Layer[] 必須 1〜4個。先に書いたレイヤが、後が手前に描かれる

ルート直下に書けるキーはこれだけです。

2.2 Layer

フィールド 必須 値域 説明
shape 文字列 必須 "circle" / "rect" / "line" / "polygon" 図形の種類
count 整数 必須 1〜200 図形の個数
sides 整数 任意 3〜24 polygon の頂点数(省略時 5)。他の shape では無視
star 数値 任意 0〜1 polygon の星形度。0=正多角形、0.5 で標準的な星。他の shape では無視
when 文字列 任意 数式 描画条件。0 と評価されたインスタンスは描かない(§2.6)
spread 文字列 任意 §2.4 個数ぶんの初期配置。省略すると全部同じ位置に重なる
x Value 必須 横位置。画面幅に対する比(0=左端、0.5=中央、1=右端)
y Value 必須 縦位置。画面高さに対する比(0=上端、1=下端)
size Value 必須 大きさ。画面高さに対する比。circle=半径 / rect=高さ / line=長さ
aspect 数値 任意 0.01〜100 rect の横幅 = size × aspect。line では太さに効く。circle では無視
rotation Value 任意 回転()。circle では無視
color 文字列 or 整数 必須 §2.5
alpha Value 必須 不透明度。0〜1 にクランプされる
wrap 真偽 任意 true で画面外へ出た図形が反対側から現れる(流れ続ける表現)

2.3 Value(値の決め方)

書き方は3通りあります。

書き方 用途
数値 "x": 0.5 定数
数式(文字列) "x": "0.5 + sin(t) * 0.2" 自由な計算・条件分岐・前の図形への依存(§2.6)
オブジェクト 下記 「基準値+乱数+波+流れ+音」の定型合成

迷ったら数式が書きやすいです。 オブジェクト形式は定型の組み合わせを短く書くための形です。

数値をそのまま書けば定数です("x": 0.5)。オブジェクト形式は次のとおりです。

{
  "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 }
}
フィールド 必須 値域 説明
base 数値 必須 基準値
random [数値, 数値] 任意 この範囲の乱数を base に足す。図形ごとに違う値になる(毎回同じ絵になる決定論的な乱数)
wave オブジェクト 任意 正弦波で揺らす(下記)
drift 数値 任意 時間で線形に動く量(1秒あたり)。負で逆方向
audio オブジェクト 任意 音に反応させる(下記)

wave

フィールド 必須 値域 説明
amplitude 数値 必須 揺れ幅
periodMs 整数 必須 50〜60000 1往復にかかる時間(ミリ秒)。小数不可
phaseByIndex 数値 任意 図形ごとに位相をずらす量。省略すると全部が一斉に同じ動きをするので、ばらけさせたいときは 0.3〜1.0 程度を入れる

audio

フィールド 必須 値域 説明
band 整数 必須 0〜63 周波数バンド。0 が最低音(キック)、数字が大きいほど高音
scale 数値 必須 バンドのレベル(0〜1)に掛けて base に足す量

低音に反応させるなら band は 0〜3、中音は 8〜16、高音は 24 以上が目安です。

2.4 spread(初期配置)

x/y の値に加算されます。

配置
"none"(省略時) 全部同じ位置。random で散らす前提のときに使う
"random" 画面全体にランダムに散らす(毎回同じ配置になる決定論的な乱数)
"row" 横一列に等間隔
"column" 縦一列に等間隔
"ring" 円周上に等間隔
"spiral" 中心から外へ渦を描く(黄金角刻みで重なりにくい)
"grid" 正方に近い格子

2.5 color(色)

パレット参照を推奨します。 ユーザーが配色を変えたときエフェクトも一緒に変わります。

意味
"text" 文字色
"accent" 強調色(サビの色)
"stroke" 縁取り色
整数 0xRRGGBB の直値(例: 赤 = 16711680)。文字列の "#ff0000" は不可

2.6 数式

値のところに文字列を書くと数式になります。200文字までです。

{ "x": "0.5 + sin(t * 2 + i * 0.5) * 0.2" }

使える変数

名前 意味
t 経過秒
at 音の経過秒(音に反応させるときはこちら)
i この図形の番号(0 から)
n 図形の総数
p i/(n-1)(0〜1 に正規化した位置。n=1 では 0)
beat 拍の進行 0〜1(0 が拍の頭)
energy セクションのエネルギー 0〜1
chorus サビ中なら 1、そうでなければ 0
aspect 画面の縦横比(幅/高さ)
px py psize prot 直前の図形の位置・大きさ・回転(§2.7)
PI TAU 円周率、その2倍

使える関数

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) — 常に正の剰余(mod(-1, 3) = 2)
  • clamp(v, min, max) / lerp(a, b, t) / step(edge, v) / smoothstep(e0, e1, v)
  • rand(k)この図形固有の乱数 0〜1(k を変えると別の系列。毎回同じ値になる決定論的な乱数)
  • band(k) — 周波数バンド k のレベル 0〜1(0 が最低音)

演算子

+ - * / % ^(べき乗)、比較 < <= > >= == !=、論理 && || !、そして三項演算子 条件 ? A : B

条件分岐は三項演算子で書きます。

{ "size": "chorus ? 0.15 : 0.05" }

書けないもの

変数への代入、ループ、関数定義、文字列は書けません。1つの式で1つの数値を出すだけです。上の表に無い名前(MathDaterandom など)を書くとエラーになります。

2.7 前の図形に依存する計算

px / py / psize / prot は、直前の図形(i-1)の確定値です。これで連鎖・積み上げが書けます。値は画面比のまま渡ります。

{ "x": "px + 0.15", "y": "py + sin(i) * 0.05" }

先頭(i = 0)では 0 から始まります。「1個目だけ別扱い」にしたいときは i で分岐してください。

{ "size": "i == 0 ? 0.1 : psize * 0.8" }

3. よくある間違い(禁止事項)

  • 値域違反: count: 0300periodMs: 10100000band: 64layers が 5個以上はすべて不可
  • 存在しないプロパティ: Layer に書けるのは §2.2 のもの、Value のオブジェクト形式に書けるのは §2.3 の5個だけ。speed, opacity, radius, points などは存在しません
  • 数式に無い名前を書く: Math.sin(t) は不可(sin(t) と書く)。Date, random, window などは使えません
  • 数式にループや代入を書く: for, =, let は書けません。1つの式で1つの数値です
  • 色を文字列で書く: "#ff0066""red" は不可。パレット名か整数
  • 小数の禁止箇所: count / periodMs / band は整数のみ
  • 単位の取り違え: x/y/size はピクセルではなく画面比"size": 40 は「画面高さの40倍」になり画面を埋め尽くします。大きさは 0.01〜0.2 程度が普通です
  • phaseByIndex の付け忘れ: 付けないと全図形が一斉に同じ動きをして、機械的で不自然に見えます
  • JSON 以外の混入: コメント、末尾カンマは不可(数式は文字列として書けます — §2.6)

4. 作例

いずれもこのままアプリに貼って通る完全な JSON です。

4.1 ふわふわ漂う光の粒

ランダムに散らばった円がゆっくり上へ流れ、横に揺れながら明滅します。画面上端を抜けたら下から再登場します。

{
  "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 音に反応するイコライザー

画面下部に並んだ縦棒が、低音から高音まで各バンドのレベルで伸び縮みします。

{
  "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 斜めに流れる線

背景を斜めの線がゆっくり横切り続けます。奥に薄い層、手前に濃い層を重ねて奥行きを出しています。

{
  "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 キックで脈打つ輪

円周上に並んだ点が、低音(キック)に合わせて外側へ広がります。

{
  "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 サビだけ舞う星(条件分岐 + 多角形)

サビの間だけ星形が現れて回ります。when でレイヤごと出し入れし、大きさは拍で脈打ちます。

{
  "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 連なって伸びる線(前の図形に依存)

1本目から順に、直前の線の終点あたりへ次の線を置いて連鎖させます。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 用プロンプトテンプレート

以下の枠内をコピーして AI に渡してください。この仕様書全体を先に貼り付け、その直後にテンプレートを続ける使い方を推奨します(仕様書なしでテンプレート単体を渡すと AI が値域や単位を誤ります)。

ここからコピー

あなたは movlyric の背景エフェクト設計者です。上に貼り付けた「GraphicSpec 仕様書」に厳密に従い、GraphicSpec の JSON を1つ作成してください。

出力規則:
- JSON のみを出力する(説明文・前置き・後書きは書かない。JSON をコードフェンスで囲むのは可)
- ルートは {"layers": [...]} で、layers は 1〜4 個
- Layer に書けるのは shape, count, sides, star, when, spread, x, y, size, aspect, rotation, color, alpha, wrap のみ
- shape は "circle" / "rect" / "line" / "polygon" のいずれか。polygon は sides(3..24) と star(0..1) を持てる
- count は 1〜200 の整数
- x, y, size, alpha は必須。次の3通りで書ける:
  (a) 数値そのまま(定数)
  (b) **数式の文字列**(200文字まで)
  (c) {"base":..., "random":[min,max], "wave":{"amplitude":...,"periodMs":整数50..60000,"phaseByIndex":...}, "drift":..., "audio":{"band":整数0..63,"scale":...}}
- 数式で使える変数は t, at, i, n, p, beat, energy, chorus, aspect, px, py, psize, prot, PI, TAU のみ
- 数式で使える関数は 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) のみ
- 数式では代入・ループ・関数定義・文字列は書けない。条件分岐は三項演算子 `条件 ? A : B` で書く
- **Math.sin(t) のような書き方は不可**(sin(t) と書く)。Date / random / window などの名前は使えない
- 直前の図形に依存させたいときは px / py / psize / prot を使う(画面比のまま渡る。i=0 では 0)
- レイヤごと出し入れしたいときは when に数式を書く(0 と評価されたら描かれない)
- **x/y/size は画面に対する比**(ピクセルではない)。size は 0.01〜0.2 程度が普通。x/y は 0〜1 が画面内
- color は "text" / "accent" / "stroke" のいずれか、または 0xRRGGBB の整数。文字列の色コードは不可
- 複数の図形を動かすときは wave に phaseByIndex を入れて位相をずらす(付けないと全部が一斉に動いて不自然になる)
- 音に反応させるときは audio を使う。band 0 が最低音(キック)、数字が大きいほど高音
- 画面外へ流し続けるときは drift と wrap:true を組み合わせる
- 存在しないプロパティ・コメント・末尾カンマ・式を書かない

作りたいエフェクト:
{ここに作りたい動きを書く}

(既存のエフェクトを修正したい場合のみ、以下に現在の JSON を貼ってください。新規作成の場合はこの節ごと削除してください)
現在の JSON:
{現在の JSON}

ここまでコピー