Song 합성에서 헷갈리기 쉬운 Score와 Note의 사양을 정리합니다.
구현의 근거는 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',
);
생성자 인수
| 인수 | 타입 | 설명 |
|---|
length | int | 프레임 길이. 초 수 × 93.75를 정수화한 값 |
lyric | string | 가사. 쉼표는 빈 문자열 '' |
key | int|null | MIDI 노트 번호. 쉼표는 null |
id | string|null | FrameAudioQuery 생성 시 note_id로 복사되는 보조 ID |
프레임 길이 계산
length는 프레임 단위입니다. 초 수에서는 다음으로 구합니다.
이 값은 직관적이지 않기 때문에, 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 노트 번호의 기준
| Note | MIDI |
|---|
| C4 | 60 |
| D4 | 62 |
| E4 | 64 |
| F4 | 65 |
| G4 | 67 |
| A4 | 69 |
| B4 | 71 |
| C5 | 72 |
Score 클래스
Score는 Note의 배열을 가진 악보 오브젝트입니다.
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으로 관리하는 편이 운영하기 쉽습니다.
- 독자 포맷 JSON을 저장
- 읽어와
Note / Score로 변환
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');
관련 페이지