Skip to main content

Goal

This page explains Score and Note details for song synthesis. The behavior is based on Revolution\Voicevox\Song\Note, Score, and SongAudioQuery.

Note class

Note represents one musical note (or one rest).
use Revolution\Voicevox\Song\Note;

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

Constructor arguments

ArgumentTypeDescription
lengthintFrame length. Integer value based on seconds × 93.75
lyricstringLyrics text. Use '' for rests
keyint|nullMIDI note number. Use null for rests
idstring|nullOptional ID copied to note_id in FrameAudioQuery generation

Frame-length calculation

length is in frames. From seconds:
length = seconds × 93.75
This is not very intuitive, so Note::len() is the practical helper for MIDI-oriented workflows.
Note::len(int $ticks, int $bpm = 125): int
  • Uses the common MIDI assumption: quarter note = 480 ticks
  • Default tempo is 125 BPM
  • Internally rounds to integer with round(), so long songs can accumulate small rounding errors
Note::make(length: Note::len(480, 120), lyric: 'ド', key: 60);  // quarter note
Note::make(length: Note::len(960, 120), lyric: 'ミ', key: 64);  // half note
Note::len() is convenient, but keep cumulative rounding error in mind for long compositions.

Rest representation

A rest is expressed as:
Note::make(length: 15, lyric: '', key: null);

MIDI note quick table

NoteMIDI
C460
D462
E464
F465
G467
A469
B471
C572

Score class

Score is a container for an array of Note objects.
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),
]);

Critical rule

The first note must always be a rest.

Main API

  • Score::make(array $notes) to create a score
  • add(Note $note) to append one note
  • toJson() to serialize
$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 internally normalizes array input and handles empty-string/null lyric cases, so it stays safe with Laravel’s ConvertEmptyStringsToNull middleware in form-driven flows.
In production, storing chart data in JSON is usually more maintainable than hardcoding long note arrays in PHP.
  1. Save your own chart format as JSON
  2. Load JSON and convert into Note / Score
  3. Pass it to Voicevox::song() for synthesis

JSON example

{
  "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" }
  ]
}

Load and synthesize

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');
Last modified on May 23, 2026