메인 콘텐츠로 건너뛰기

목적

Song 합성에서 헷갈리기 쉬운 ScoreNote의 사양을 정리합니다. 구현의 근거는 Revolution\Voicevox\Song\Note / Score / SongAudioQuery의 소스입니다.

Note 클래스

Note는 1개의 음표 (또는 쉼표)를 나타냅니다.
use Revolution\Voicevox\Song\Note;

$note = Note::make(
    length: 94,
    lyric: 'ド',
    key: 60,
    id: 'intro-1',
);

생성자 인수

인수타입설명
lengthint프레임 길이. 초 수 × 93.75를 정수화한 값
lyricstring가사. 쉼표는 빈 문자열 ''
keyint|nullMIDI 노트 번호. 쉼표는 null
idstring|nullFrameAudioQuery 생성 시 note_id로 복사되는 보조 ID

프레임 길이 계산

length는 프레임 단위입니다. 초 수에서는 다음으로 구합니다.
length = 초 수 × 93.75
이 값은 직관적이지 않기 때문에, MIDI에 익숙한 경우에는 Note::len()을 사용하는 것이 실용적입니다.
Note::len(int $ticks, int $bpm = 125): int
  • 4분음표를 480 ticks로 취급한다는 전제
  • 125 BPM이 기본값
  • 내부에서 round()로 정수화하기 때문에 긴 곡에서는 반올림 오차가 서서히 누적됩니다
Note::make(length: Note::len(480, 120), lyric: 'ド', key: 60);  // 4분음표
Note::make(length: Note::len(960, 120), lyric: 'ミ', key: 64);  // 2분음표
Note::len()은 편리하지만, 긴 악곡에서는 누적 오차에 주의하세요. 필요하면 섹션 단위로 길이를 재조정합니다.

쉼표 표현

쉼표는 다음 조합입니다.
Note::make(length: 15, lyric: '', key: null);

MIDI 노트 번호의 기준

NoteMIDI
C460
D462
E464
F465
G467
A469
B471
C572

Score 클래스

ScoreNote의 배열을 가진 악보 오브젝트입니다.
use Revolution\Voicevox\Song\Score;

$score = Score::make([
    Note::make(length: 15, lyric: '', key: null),
    Note::make(length: Note::len(480, 120), lyric: 'ド', key: 60),
]);

중요 규칙

최초의 노트는 반드시 쉼표로 하세요.

주요 API

  • Score::make(array $notes)로 생성
  • add(Note $note)로 추가
  • toJson()으로 JSON 문자열화
$score = Score::make([
    Note::make(length: 15, lyric: '', key: null),
]);

$score->add(Note::make(length: Note::len(480, 120), lyric: 'ド', key: 60));

$json = $score->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Score 생성자는 배열 입력 시에 lyric의 빈 문자열/null을 흡수하는 구현이 있어, ConvertEmptyStringsToNull 미들웨어를 경유한 폼 입력에서도 다루기 쉽게 되어 있습니다.

실전 패턴: JSON 파일 관리 (권장)

실제 프로덕트에서는 PHP에 악보를 직접 쓰지 말고 JSON으로 관리하는 편이 운영하기 쉽습니다.
  1. 독자 포맷 JSON을 저장
  2. 읽어와 Note / Score로 변환
  3. Voicevox::song()에 전달해 생성

JSON 예

{
  "teacher": 6000,
  "speaker": 3001,
  "bpm": 120,
  "notes": [
    { "ticks": 120, "lyric": "", "key": null, "id": "r0" },
    { "ticks": 480, "lyric": "ド", "key": 60, "id": "n1" },
    { "ticks": 480, "lyric": "レ", "key": 62, "id": "n2" },
    { "ticks": 960, "lyric": "ミ", "key": 64, "id": "n3" },
    { "ticks": 120, "lyric": "", "key": null, "id": "r1" }
  ]
}

읽어오기와 합성

use Illuminate\Support\Facades\Storage;
use Revolution\Voicevox\Song\Note;
use Revolution\Voicevox\Song\Score;
use Revolution\Voicevox\Voicevox;

$chart = json_decode(
    Storage::disk('local')->get('songs/sample.json'),
    true,
    flags: JSON_THROW_ON_ERROR,
);

$score = Score::make(
    collect($chart['notes'])
        ->map(fn (array $note) => Note::make(
            length: Note::len(ticks: $note['ticks'], bpm: $chart['bpm']),
            lyric: $note['lyric'] ?? '',
            key: $note['key'],
            id: $note['id'] ?? null,
        ))
        ->all(),
);

$response = Voicevox::song($score, teacher: $chart['teacher'])
    ->generate(id: $chart['speaker']);

$response->storeAs('songs', 'sample.wav');

관련 페이지

마지막 수정일 2026년 7월 13일