Files

13 KiB
Raw Permalink Blame History

VGM Format Reference

Источник истины: src_sdcc/inc/vgm.h, src_sdcc/lib/vgm.c

Обзор формата VGM

VGM (Video Game Music) — формат хранения логов регистров звуковых чипов.

Основные характеристики

  • Расширение файла: .vgm (raw) или .vgz (gzip-сжатый)
  • Содержит команды записи в регистры звуковых чипов
  • Поддерживаемые плагином чипы: YMF262 (OPL3), YM3812 (OPL2), YM3526 (OPL1), Y8950, AY-3-8910/YM2149/YM2203, SAA1099
  • Частота семплирования фиксирована: 44100 Гц

Структура заголовка VGM (ключевые смещения)

Все поля — little-endian 32 бит, если не указано иное.
Минимальная поддерживаемая версия: 1.00 (0x00000100).

Offset Размер Название Описание С версии
0x00 4 Ident Сигнатура "Vgm " (0x56 0x67 0x6D 0x20) 1.00
0x04 4 EoF offset Расстояние до EOF от позиции 0x04 1.00
0x08 4 Version BCD: 0x0150=v1.50, 0x0151=v1.51 1.00
0x0C 4 SN76489 clock Тактовая частота SN76489 (0 = нет) 1.00
0x10 4 YM2413 clock Тактовая частота YM2413 / OPLL (0 = нет) 1.00
0x14 4 GD3 offset Смещение GD3-тега от 0x14 (0 = нет тега) 1.00
0x18 4 Total samples Полная длина трека в семплах (÷44100 = сек) 1.00
0x1C 4 Loop offset Смещение точки петли от 0x1C (0 = нет) 1.00
0x20 4 Loop samples Длина тела петли в семплах 1.00
0x24 4 Rate Кадровая частота (50/60 Гц, информативно) 1.01
0x34 4 VGM data offset Смещение VGM-данных от 0x34; 0 → данные с 0x40 1.50
0x44 4 YM2203 clock YM2203 (OPN/PSG) (0 = нет) 1.51
0x50 4 YM3812 clock YM3812 (OPL2) — AdLib/SoundBlaster 1.51
0x54 4 YM3526 clock YM3526 (OPL1) — ISA AdLib 1.51
0x58 4 Y8950 clock Y8950 (OPL+ADPCM) — MSX-Audio 1.51
0x5C 4 YMF262 clock YMF262 (OPL3) — SB Pro 2 / TSConfig 1.51
0x60 4 YMF278B clock YMF278B (OPL4) (0 = нет) 1.51
0x74 4 AY8910 clock AY-3-8910 / YM2149 (0 = нет) 1.51
0xC8 4 SAA1099 clock SAA1099 — SAM Coupé (0 = нет) 1.71

⚠️ Частая ошибка в документации: смещения чипов в диапазоне 0x50–0x5C — не 0x5C/0x60/0x64/0x68.
Используйте константы: VGM_OFF_YM3812=0x50, VGM_OFF_YM3526=0x54, VGM_OFF_Y8950=0x58, VGM_OFF_YMF262=0x5C.

Смещение данных VGM

Если заголовок[0x34] (VGM data offset) == 0:
    данные начинаются с абсолютного адреса 0x40  (старые файлы, < v1.50)
Иначе:
    данные начинаются с 0x34 + заголовок[0x34]

Типичное значение для v1.51+: VGM_data_offset = 0x4C, данные с 0x80.
Реализация в vgm_parse_header(): хранит готовый адрес начала данных в vgm_data_start.

Bit 30 поля clock — флаг dual-chip

Бит 30 любого clock-поля (clock & 0x40000000) означает два экземпляра чипа (VGM_CF_DUAL).
Реальная тактовая частота = clock & 0x3FFFFFFF.

Обнаружение чипов — vgm_parse_header()

Приоритет определения vgm_chip_type: YMF262 > YM3812 > YM3526/Y8950:

// vgm.h — константы
#define VGM_OFF_YM3812   0x50   // OPL2
#define VGM_OFF_YM3526   0x54   // OPL1
#define VGM_OFF_Y8950    0x58   // OPL+ADPCM
#define VGM_OFF_YMF262   0x5C   // OPL3
#define VGM_OFF_AY8910   0x74   // AY-3-8910 / YM2149
#define VGM_OFF_YM2203   0x44   // OPN (PSG-часть = AY)
#define VGM_OFF_SAA1099  0xC8   // SAA1099

// Значения vgm_chip_type
#define VGM_CHIP_NONE    0
#define VGM_CHIP_OPL     1   // YM3526 или Y8950
#define VGM_CHIP_OPL2    2   // YM3812
#define VGM_CHIP_OPL3    3   // YMF262

VGM-команды — полная таблица

Код Байт Параметры Описание
0x55 3 rr dd YM2203 write (PSG-часть, reg 0–15)
0x5A 3 rr dd YM3812 (OPL2) write
0x5B 3 rr dd YM3526 (OPL1) write
0x5C 3 rr dd Y8950 (OPL+ADPCM) write
0x5E 3 rr dd YMF262 (OPL3) Bank 0 write
0x5F 3 rr dd YMF262 (OPL3) Bank 1 write
0x61 3 lo hi Задержка: (hi<<8)|lo семплов
0x62 1 — Задержка 735 семплов (1/60 с)
0x63 1 — Задержка 882 семпла (1/50 с)
0x66 1 — End of data → конец потока / loop
0x67 var — Data block (PCM и пр.) — пропускается
0x70–0x7F 1 — Задержка 1–16 семплов ((cmd & 0x0F) + 1)
0xA0 3 rr dd AY8910 write; бит 7 rr = чип 2
0xBD 3 rr dd SAA1099 write; бит 7 rr = чип 2
0xD0 4 pp aa dd YMF278B (OPL4) write, порт pp

Прочие команды пропускаются по таблице длин VGM spec (размер 1, 2, или 3 байта).

0xD0 — YMF278B (OPL4) write

0xD0 pp aa dd
  • pp бит 7 — выбор чипа (dual-chip); игнорируется, на ZXM-MoonSound один чип.
  • pp & 0x7F — порт:
    • 0 — FM Bank 0 (те же регистры/порты #C4-#C5, что и 0x5E для OPL3)
    • 1 — FM Bank 1 (те же регистры/порты #C6-#C7, что и 0x5F для OPL3)
    • 2 — wave-часть чипа (табличный синтез), порты #7E (адрес) / #7F (данные)
  • aa — номер регистра, dd — значение.

Чтобы чип реально принимал запись в порт 2 (wave-часть), в Bank 1 регистре 0x05 должен быть установлен бит NEW2 (бит 1), в дополнение к обычному биту NEW (бит 0) — см. CMDBLK_INIT_OPL4 в cmdblocks.asm. Без NEW2 запись в порты #7E/#7F реальным чипом YMF278B игнорируется.


Масштабирование задержек

VGM sample rate = 44100 Гц, ISR rate = 1367 Гц (= 44100 / 32):

// inc/isr.h
#define VGM_SAMPLE_SHIFT  5      // делитель = 2^5 = 32
#define VGM_SAMPLE_MASK   0x1Fu  // маска остатка

Алгоритм vgm_fill_buffer():

  • Накапливает семплы задержек
  • При накоплении ≥ 32 → конвертирует samples >> 5 в ISR-тики → команда CMD_WAIT
  • Задержки < 32 семплов (< 0.73 мс) теряются (rounded down, это нормально)
1 секунда      = 44100 семплов = 1378 ISR-тиков ≈ 1367 Гц
1 фрейм (50Hz) =   882 семпла  =   27 ISR-тиков
1 фрейм (60Hz) =   735 семплов =   22 ISR-тика

GD3 — метаданные трека

GD3-тег содержит Unicode-строки (UCS-2 LE, нуль-терминированные):

№ Содержимое
1 Track title (EN)
2 Track title (JP)
3 Game name (EN)
4 Game name (JP)
5 System name (EN)
6 System name (JP)
7 Author (EN)
8 Author (JP)
9 Release date
10 VGM pack author
11 Notes

В плагине читаются только английские поля (нечётные), конвертируются в ASCII: символы > 127 → '?'.
Макс. длина поля: VGM_GD3_LEN = 48 байт.

// inc/vgm.h
extern char vgm_gd3_track[VGM_GD3_LEN];
extern char vgm_gd3_game[VGM_GD3_LEN];
extern char vgm_gd3_system[VGM_GD3_LEN];
extern char vgm_gd3_author[VGM_GD3_LEN];

Петля (loop)

// inc/vgm.h
extern uint16_t vgm_loop_addr;  // адрес точки петли в окне #C000 (0 = нет)
extern uint8_t  vgm_loop_page;  // VPL-страница точки петли

Если loop_offset (0x1C) != 0:

  • Абсолютное смещение в файле = 0x1C + loop_offset
  • vgm_rewind_to_loop() переставляет vgm_read_ptr / vgm_cur_page на точку петли

VGZ — gzip-сжатый VGM

Файл .vgz — стандартный gzip. Схема обработки в плагине:

1. Загрузить .vgz в мегабуфер (VPL-страницы выше #20)
2. copy_pages_to_tap(n)  — скопировать во временную TAP-область (#A1–#BF, 31 стр.)
3. inflate_vgz(src, dst) — распаковать (inflate.asm) обратно в мегабуфер
4. Работать с VGM как обычно

Макс. размер распакованного VGM через TAP: 31 × 16 КБ = 496 КБ.

void copy_pages_to_tap(uint8_t n_pages);
uint8_t inflate_vgz(uint8_t src_page, uint8_t dst_page);
// возвращает: 0 = успех, 1 = ошибка (не gzip или CRC-ошибка)

OPL2 vs OPL3 — проблема L/R output

Проблема: opl3_init() устанавливает NEW=1 (OPL3 mode).
В OPL3 mode биты 4–5 регистров C0–C8 управляют маршрутизацией L/R (L=1, R=1 для звука).
Файлы OPL2/OPL1 (команды 0x5A/0x5B) пишут C0 без битов L/R → тишина.

Решение в start_playback() (main.c):

opl3_init();                           // NEW=1 по умолчанию
if (vgm_chip_type != VGM_CHIP_OPL3) {
    opl3_write_b1(0x05, 0x00);         // NEW=0: OPL2-совместимый режим
}
// В OPL2-compat звук всегда идёт на оба канала L+R

Пример парсинга заголовка (SDCC C)

#include "inc/vgm.h"
#include "inc/wc_api.h"

// Загрузить первую страницу файла в окно #C000 и разобрать заголовок
wc_mngcvpl(0);           // Переключить Win3 (#C000) на VPL-страницу 0
wc_load512(0xC000, 32);  // Загрузить 16 КБ с диска

uint8_t rc = vgm_parse_header();

switch (rc) {
case VGM_OK:
    // vgm_chip_type  → VGM_CHIP_OPL / OPL2 / OPL3
    // vgm_chip_count → кол-во обнаруженных чипов
    // vgm_version    → 0x0151 для v1.51
    // vgm_loop_addr  → 0 = нет петли
    break;
case VGM_ERR_HEADER:
    // Нет сигнатуры "Vgm " или заголовок повреждён
    break;
case VGM_ERR_NOOPL:
    // Файл не содержит поддерживаемого чипа (OPL/AY/SAA)
    break;
}

Полезные константы (из vgm.h)

// Смещения clock-полей в заголовке (relative byte offsets)
#define VGM_OFF_SN76489  0x0C
#define VGM_OFF_YM2413   0x10
#define VGM_OFF_YM2203   0x44   // OPN
#define VGM_OFF_YM3812   0x50   // OPL2   ← основной
#define VGM_OFF_YM3526   0x54   // OPL1
#define VGM_OFF_Y8950    0x58   // MSX-Audio
#define VGM_OFF_YMF262   0x5C   // OPL3   ← основной
#define VGM_OFF_YMF278B  0x60   // OPL4
#define VGM_OFF_AY8910   0x74   // AY-3-8910 / YM2149
#define VGM_OFF_SAA1099  0xC8   // SAA1099

// Коды результата vgm_parse_header()
#define VGM_OK           0
#define VGM_ERR_HEADER   1
#define VGM_ERR_NOOPL    2

// ISR / VGM sample rate mapping
#define VGM_SAMPLE_SHIFT 5   // 44100 / 32 ≈ 1367 Гц (ISR_FREQ)

Ссылки