Files

294 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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**:
```c
// 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):
```c
// 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` байт.
```c
// 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)
```c
// 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 КБ**.
```c
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):**
```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)
```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`)
```c
// Смещения 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)
```
---
## Ссылки
- VGM Format Specification: https://vgmrips.net/wiki/VGM_Specification
- YMF262 (OPL3) Datasheet: Yamaha YMF262 Application Manual
- Архив VGM-треков: https://vgmrips.net/