사운드 디자인
대부분의 AI 생성 모션 그래픽은 무음입니다. 사운드는 작품을 프로페셔널하게 만듭니다.
sfx/ 엔진은 순수 Python 표준 라이브러리로 모든 사운드를 합성합니다. 샘플, 라이선스, 설치가 필요 없으며 매 실행마다 동일한 출력을 보장합니다. 영상과 동일한 타임라인 JSON을 읽기 때문에 사운드가 싱크에서 밀려날 수 없습니다.
구성 요소
| 파일 | 역할 |
|---|---|
sfx/synth.py | 제너레이터: 각각 48kHz 모노 샘플을 반환 |
sfx/mix.py | Mix: 사운드를 시간轴上 배치, 라우드니스 목표로 마스터링, WAV 파일 작성 |
sfx/loudness.py | BS.1770 통합 라우드니스 (ffmpeg의 ebur128과 일치) |
sfx/cues/<video>.py | 큐 시트: 한 비디오의 사운드 디자인 |
python3 -m sfx --list # 각 용도별 제너레이터 목록
python3 -m sfx sfx/cues/<video>.py # 렌더링 → 마스터링된 WAV
새 비디오를 위한 큐 시트 작성
sfx/cues/<video>.py 파일을 생성하세요:
from sfx.synth import click, pop, whoosh, chime, riser, impact
TIMELINE = "timeline.json" # 컴포지션이 읽는 동일한 파일
OUT = "public/sfx.wav"
TARGET_LUFS = -14.0 # 선택 사항, 기본값
def score(mix, tl):
s1 = tl["s1"]
mix.put(s1["title"], whoosh(0.4, 500, 2400, peak=0.45), 0.12)
for i, t in enumerate(s1["items"]):
mix.put(t, pop(900 + i * 150, 400), 0.3, pan=-0.4 + i * 0.3)
mix.put(t, samples, gain, pan):t는 항상tl에서 가져와야 하며, 직접 숫자를 입력하면 안 됩니다. 비트로부터의 오프셋(s1["exit"] + 0.05)은 괜찮습니다.mix.typing(start, end, chars): 문자당 하나의 인간미 있는 키스트로크를 생성합니다.mix.put_stereo(t, left, right, gain):pad()와 같은 스테레오 소스용입니다.- 마스터링은 자동입니다: 엔진이 소프트 클리핑을 적용하고 게인을 조절하여
TARGET_LUFS에 도달하도록 하며, 트루 피크가 −2 dBFS 이하로 유지되도록 드라이브를 필요한 만큼만 높입니다(AAC 오버슈트를 고려하여 MP4가 −1 dBTP 미만으로 유지되도록). 대부분 짧은 히트와 그 사이의 무음으로 구성된 믹스는 압축 없이 -14에 도달할 수 없습니다. 이 경우 엔진이 더 낮은 출력을 내고 그 사실을 알려줍니다. 강제로 맞추지 말고 지속 사운드(패드, 더 긴 테일)로 해결하세요. package.json에 새 큐 시트를 가리키는"sfx"스크립트를 추가하거나 직접 실행하세요.
sfx/cues/effort.py는 128개의 사운드와 패드 베드를 포함한 완전한 실제 예제입니다.
시각적 이벤트 → 사운드
| 화면 | 제너레이터 | 참고 |
|---|---|---|
| 타이핑 | mix.typing() | ±4–6ms 지터, 무작위 게인 |
| 단어/라인 나타남 | whoosh() + 착지 시 blip() | 짧고 경쾌하게 |
| 배지, 칩, 콜아웃 팝인 | pop() | 시퀀스 전반에 걸쳐 피치 상승 |
| 컨트롤이 스냅됨 | click() + 키에 맞는 blip() | |
| 스케치/그리기 | scratch() | |
| 빌드업 | riser() | 히트 지점에서 정확히 끝남; 비디오당 하나의 큰 라이저 |
| 성공/도착 | chime() | 키에 맞는 코드 |
| 히어로 전환 | whoosh() 인, 히트 시 impact() | 비디오당 한 개의 임팩트 |
| 모든 배경 | envelope()와 함께 pad() | 히어로 전환 아래에서 덕킹 |
강조하되 서술하지 마세요: 모든 모션에 사운드가 필요한 것은 아닙니다. 연속 모션(드리프트, 그레인, 회전)은 무음으로 유지합니다.
레벨과 공간
- 히어로 히트: 게인 0.25–0.35. 보조 사운드: 0.08–0.2. 패드: ~0.03, 히어로 전환 아래에서 약 65% 덕킹.
- 패닝은 화면상의 위치를 따릅니다(−1 왼쪽 … +1 오른쪽). 시퀀스는 스테레오 필드를 가로질러 스윕합니다.
- 하나의 키를 선택하고 모든 음조 사운드를 그 키로 유지하세요. 진행에 따라 피치가 상승하고 결과물에서 해소됩니다. (effort 예제에서 패드와 슬라이더 노트는 D 메이저이지만, 씬 3의 팝과 차임은 여전히 C 메이저입니다. 그걸 따라 하지 마세요.)
제너레이터 추가
SR에서 float 리스트를 반환하는 함수를 sfx/synth.py에 작성하세요. 첫 번째 독스트링 줄에 용도를 적으세요. --list가 표시합니다. 노이즈에는 모듈 수준의 random을 사용하여 렌더링이 재현 가능하도록 유지하세요. 경쾌한 사운드에는 bandnoise()를 기반으로 구축하세요.
검증
npm run sfx # 또는 python3 -m sfx sfx/cues/<video>.py
npm run wave # out/wave.png + WAV의 라우드니스
npm run build
node scripts/wave.mjs out/effort.mp4 # 실제로 게시할 영상의 라우드니스
out/wave.png에서 모든 산호색 선은 타임라인의 비트입니다. 각 트랜지언트는 선 위 또는 약간 뒤에 위치해야 합니다. 선 사이에 히트가 있으면 하드코딩된 시간 또는 오래된 WAV를 의미합니다. 자체 타임라인 파일이 있는 비디오의 경우 --timeline을 전달하세요.
선택 사항: 생성된 오디오
합성할 수 없는 음성 해설이나 사운드의 경우, ElevenLabs MCP(.mcp.json.example 참조)를 사용하여 public/에 파일을 생성할 수 있습니다. <Sequence from={beat * FPS}> 내부에 <Audio src={staticFile(...)} />를 사용하여 배치하세요.