跳转到主要内容

目的

整理 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|null生成 FrameAudioQuery 时会拷贝到 note_id 的辅助 ID

帧长计算

length 的单位是帧。从秒数换算如下。
length = 秒数 × 93.75
由于该值并不直观,如果你熟悉 MIDI,使用 Note::len() 更实用。
Note::len(int $ticks, int $bpm = 125): int
  • 前提是将四分音符视为 480 ticks
  • 默认 125 BPM
  • 内部通过 round() 取整,因此长曲子中会逐渐累积舍入误差
Note::make(length: Note::len(480, 120), lyric: 'ド', key: 60);  // 四分音符
Note::make(length: Note::len(960, 120), lyric: 'ミ', key: 64);  // 二分音符
Note::len() 虽然方便,但在长曲子中请注意累积误差。必要时可以按段落重新调整长度。

休止符的表示

休止符通过以下组合表示。
Note::make(length: 15, lyric: '', key: null);

MIDI 音符编号参考

NoteMIDI
C460
D462
E464
F465
G467
A469
B471
C572

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),
]);

重要规则

第一个 note 必须是休止符。

主要 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日