GraphicSpec 사양서 — 나만의 "배경 이펙트" 만들기

이 문서는 일본어 원문의 번역본입니다. 일본어판이 정본이며, 내용에 차이가 있을 경우 일본어판이 우선합니다.

이 문서는 movlyric의 배경 이펙트(가사 뒤에 도형·선을 그리는 것)의 정의인 "GraphicSpec"의 완전한 사양입니다. 앱의 "스타일 에디터 → 「배경」 탭 → 배경 이펙트 → 「사용자 지정」 → AI로 만들기…"에서 사용합니다(가사 글자 자체를 움직이는 "문자 움직임"은 별개이며, 그쪽은 ANIMATION_SPEC(문자 움직임)이 사양입니다). 이 문서만 읽은 AI(또는 사람)가 앱에 그대로 붙여 넣어 통과하는 JSON을 작성할 수 있게 하는 것이 목적입니다. 문서 끝에 AI에게 전달할 프롬프트 템플릿이 있습니다. 이 문서 전체를 프롬프트의 일부로 붙여 넣어도 작동합니다.

1. 개요

배경 이펙트는 가사의 뒤쪽에서 움직이는 모션 그래픽입니다(가사 자체의 움직임은 별도 사양인 AnimationSpec이 담당합니다).

GraphicSpec은 "같은 모양의 도형을 N개 나열해 움직인다"는 형태로 작성합니다. 도형은 원·직사각형·선·다각형의 4종류입니다.

각 도형의 위치·크기·색상·불투명도는 두 가지 방식으로 정할 수 있습니다.

  1. 정형화된 합성(기준값 + 난수 + 사인파 + 흐름 + 소리) — 짧게 쓸 수 있음
  2. 수식 — 0.5 + sin(t * 2 + i) * 0.2처럼 자유롭게 계산할 수 있음. 조건 분기나, 직전 도형의 위치를 참조한 연쇄 배치도 쓸 수 있음

수식은 JavaScript가 아닙니다. 대입·반복·함수 정의는 쓸 수 없고, 하나의 식이 하나의 숫자를 반환할 뿐입니다(표 계산 소프트웨어의 수식과 같은 개념). 사용할 수 있는 이름은 §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 문자열 또는 정수 필수 §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 숫자 선택 — 시간에 따라 선형으로 움직이는 양(초당). 음수면 반대 방향
audio 객체 선택 — 소리에 반응시킴(아래 참고)

wave

필드 타입 필수 값 범위 설명
amplitude 숫자 필수 — 흔들림 폭
periodMs 정수 필수 50~60000 1왕복에 걸리는 시간(밀리초). 소수 불가
phaseByIndex 숫자 선택 — 도형마다 위상을 어긋나게 하는 양. 생략하면 전부가 일제히 같은 움직임을 함으로, 흩어지게 하고 싶을 때는 0.3~1.0 정도를 넣는다

audio

필드 타입 필수 값 범위 설명
band 정수 필수 0~63 주파수 밴드. 0이 최저음(킥), 숫자가 클수록 고음
scale 숫자 필수 — 밴드의 레벨(0~1)에 곱해 base에 더하는 양

저음에 반응시키려면 band는 03, 중음은 816, 고음은 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" }

쓸 수 없는 것

변수 대입, 반복, 함수 정의, 문자열은 쓸 수 없습니다. 하나의 식으로 하나의 숫자를 낼 뿐입니다. 위 표에 없는 이름(Math, Date, random 등)을 쓰면 오류가 됩니다.

2.7 앞 도형에 의존하는 계산

px / py / psize / prot는 직전 도형(i-1)의 확정값입니다. 이것으로 연쇄·쌓기를 표현할 수 있습니다. 값은 화면 비율 그대로 전달됩니다.

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

첫 번째(i = 0)에서는 0부터 시작합니다. "첫 번째만 별도 취급"하고 싶을 때는 i로 분기하세요.

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

3. 흔한 실수(금지 사항)

  • 값 범위 위반: count: 0이나 300, periodMs: 10이나 100000, band: 64, layers가 5개 이상은 모두 불가
  • 존재하지 않는 속성: Layer에 쓸 수 있는 것은 §2.2의 것, Value의 객체 형식에 쓸 수 있는 것은 §2.3의 5개뿐. speed, opacity, radius, points 등은 존재하지 않습니다
  • 수식에 없는 이름 쓰기: Math.sin(t)는 불가(sin(t)로 쓴다). Date, random, window 등은 사용할 수 없습니다
  • 수식에 반복이나 대입 쓰기: for, =, let은 쓸 수 없습니다. 하나의 식으로 하나의 숫자입니다
  • 색상을 문자열로 쓰기: "#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 이어지며 뻗어가는 선(앞 도형에 의존)

첫 번째 선부터 순서대로, 직전 선의 끝점 부근에 다음 선을 두어 연쇄시킵니다. 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을 하나 작성해 주세요.

출력 규칙:
- 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}

여기까지 복사