Add OPL4 playback and analyzer support
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
# Wild Commander — Обзор Системы Плагинов
|
||||
|
||||
> Версия документа: 2.0 — дополнена по результатам анализа исходников
|
||||
> Источники: `inc/wc_api.h`, `asm/wc_api.s`, `asm/crt0.s`, `DiskRef/Help.txt`, `DiskRef/WC_History.txt`
|
||||
|
||||
## Введение
|
||||
|
||||
Wild Commander (WC) — файловый менеджер с системой плагинов для платформы
|
||||
**TS-Configuration** (ZX Spectrum Evo). Выполняет роль основной оболочки и
|
||||
загрузчика для работы с FAT32-дисками, SD-картами и IDE.
|
||||
|
||||
WC запускается из BootROM / vDOS и предоставляет:
|
||||
- Двухпанельный интерфейс (80 × 25/30/36 TXT-режим)
|
||||
- FAT32 (длинные и короткие имена до 255 символов)
|
||||
- Систему плагинов (.WMF) с горячей загрузкой/выгрузкой
|
||||
- Поддержку устройств: SD (Z-Controller), IDE NEMO, IDE SMUC, NeoGS SD
|
||||
- Встроенный просмотрщик (F3): TXT, HEX, TGA, SCR, Gigascreen, MGS — до 1 МБ
|
||||
- Мультиязычность (ENG / RUS, переключение CTRL+SHIFT)
|
||||
|
||||
### Версии
|
||||
|
||||
| Компонент | Версия | Примечание |
|
||||
|-----------|--------|------------|
|
||||
| WC | 1.10 (03.08.2025) | Последняя из `WC_History.txt` |
|
||||
| Plugin System | **#10** | Формат заголовка `.WMF` |
|
||||
| Идентификатор | `WildCommanderMDL` | 16 байт, offset +16 в заголовке |
|
||||
|
||||
## Общая концепция
|
||||
|
||||
Плагины WC (.WMF файлы) — модульные расширения, выполняемые в адресном
|
||||
пространстве Z80. Поддерживают до **64 страниц** (1 МБ) кода/данных.
|
||||
|
||||
Типичные области применения:
|
||||
- Просмотр файлов различных форматов (BMP, GIF, ILBM, SCR)
|
||||
- Воспроизведение музыки и видео (VGM, MOD, WAV, MP3/GS)
|
||||
- Редактирование файлов (TXT Editor с подсветкой синтаксиса)
|
||||
- Работа с ROM/Flash (ROM_PROG)
|
||||
- Монтирование образов дисков (MOUNTER: TRD, TAP, FDI)
|
||||
- Утилиты (часы, скринсейвер, компилятор, дамп памяти)
|
||||
|
||||
## Типы запуска плагинов
|
||||
|
||||
При запуске плагина, в регистр **A** передается тип вызова:
|
||||
|
||||
| Код | Константа (C) | Описание |
|
||||
|-----|---------------|----------|
|
||||
| #00 | `WC_PCOND_EXT_ONLY` | По расширению файла (ENTER / F3) |
|
||||
| #01 | `WC_PCOND_ON_LOAD` | При первой загрузке плагина (ENTER) |
|
||||
| #02 | `WC_PCOND_TIMER` | По таймеру скринсейвера + меню F10 |
|
||||
| #03 | `WC_PCOND_F10` | Из меню запуска плагинов (F10) |
|
||||
| #04 | `WC_PCOND_VIEWER` | Из меню вьювера (SHIFT+F3 / SHIFT+ENTER) |
|
||||
| #05 | `WC_PCOND_F10_VIEW` | F10 + SHIFT+F3 |
|
||||
| #11 | `WC_PCOND_F10_F2` | F10 + F2 |
|
||||
| #12 | `WC_PCOND_F2` | По нажатию F2 |
|
||||
| #13 | `WC_PCOND_F10_F4` | F10 + F4 |
|
||||
| #14 | `WC_PCOND_F4` | По нажатию F4 |
|
||||
| #15 | `WC_PCOND_F10_V_F4` | F10 + SHIFT+F3 + F4 |
|
||||
|
||||
> **Примечание:** Все плагины также вызываются по расширению, если расширения
|
||||
> указаны в блоке расширений заголовка (offset +64). ENTER и F3 разделены
|
||||
> начиная с WC v1.00 — флаг +63 определяет, какой из них запускает плагин.
|
||||
|
||||
## Входные параметры при запуске
|
||||
|
||||
WC делает `CALL #8000`. В `crt0.s` (SDCC) входные регистры сохраняются
|
||||
в глобальные переменные автоматически.
|
||||
|
||||
```asm
|
||||
; Входные параметры:
|
||||
A - тип вызова (см. таблицу выше)
|
||||
[DE,HL] - длина файла в байтах (DE=hi16, HL=lo16)
|
||||
BC - адрес строки с именем файла (для типов #00, #04, #14)
|
||||
формат: name(1-255), 0 (длинное FAT имя)
|
||||
IX - адрес структуры активной панели
|
||||
|
||||
; Альтернативные регистры (только для #00,#04,#12,#14):
|
||||
A' - номер расширения (0-31) для типа #00
|
||||
DE' - количество файлов/каталогов в панели (включая "..")
|
||||
HL' - номер объекта из панели (от 1)
|
||||
```
|
||||
|
||||
В SDCC-плагине (`crt0.s` автоматически):
|
||||
```c
|
||||
extern uint8_t wc_file_ext; // ← A' (номер расширения)
|
||||
extern char * wc_file_name; // ← BC (указатель на имя)
|
||||
extern uint32_t wc_file_size; // ← [DE,HL] (32-бит размер)
|
||||
```
|
||||
|
||||
## Коды возврата из плагина
|
||||
|
||||
При выходе из плагина регистр **A** определяет действие WC:
|
||||
|
||||
| A | Константа (C) | Действие |
|
||||
|---|---------------|----------|
|
||||
| 0 | `WC_EXIT_ESC` | Просто выход (ESC) |
|
||||
| 1 | `WC_EXIT_UNRECOGNIZED` | Файл не опознан → передать другому плагину (тип #00) |
|
||||
| 2 | `WC_EXIT_NEXT` | Перейти на следующий файл (тип #00) |
|
||||
| 3 | `WC_EXIT_RELOAD_DIR` | Перечитать каталог |
|
||||
| 4 | `WC_EXIT_PREV` | Перейти на предыдущий файл (тип #00) |
|
||||
|
||||
В SDCC-плагине: `wc_exit_code = 0; return 0;` — `crt0.s` считывает
|
||||
`wc_exit_code` в A перед `RET` обратно в WC.
|
||||
|
||||
> **Код 2/4 (NEXT/PREV):** WC вызывает тот же плагин повторно с другим
|
||||
> файлом. `crt0.s` заново обнуляет BSS (`_DATA`) и копирует инициализаторы
|
||||
> (`_INITIALIZER → _INITIALIZED`), поэтому все `static` переменные сбросятся.
|
||||
|
||||
## Системные адреса
|
||||
|
||||
> Источник: `src_sdcc/inc/wc_api.h`, раздел «СИСТЕМНЫЕ ПЕРЕМЕННЫЕ WC»
|
||||
|
||||
| Адрес | Константа | Размер | Описание |
|
||||
|---------------|--------------|--------|---------------------------------------------------|
|
||||
| `#6000` | `WC_SYS_PG0` | 1 | Текущая страница в окне #0000 |
|
||||
| `#6001` | `WC_SYS_PG4` | 1 | Текущая страница в окне #4000 |
|
||||
| `#6002` | `WC_SYS_PG8` | 1 | Текущая страница в окне #8000 |
|
||||
| `#6003` | `WC_SYS_PGC` | 1 | Текущая страница в окне #C000 |
|
||||
| `#6004` | `WC_SYS_ABT` | 1 | 1 = был нажат ESC |
|
||||
| `#6005` | `WC_SYS_ENT` | 1 | 1 = был нажат ENTER |
|
||||
| `#6006` | `WC_SYS_FUN` | 3 | **Точка входа WC API** (JP к диспетчеру) |
|
||||
| `#6009` | `WC_SYS_TMN` | 2 | Таймер INT (uint16_t, ++/кадр ≈50 Гц) |
|
||||
| `#600B` | `WC_SYS_FEP` | 1 | Страница FAT-движка в окне 0 |
|
||||
| `#600C` | `WC_SYS_XPP` | 1 | Сохранённая страница |
|
||||
| `#600D` | `WC_SYS_CNFV`| 1 | ЦАП: 0=PWM, 1=3bit, 2=4bit, 3=5bit, 7=VDAC2/FT812 |
|
||||
| `#600E` | `WC_SYS_HEI` | 1 | Высота TXT-экрана: 25 / 30 / 36 |
|
||||
| `#600F` | `WC_SYS_TXTMD`| 1 | Текущий видеорежим WC (0=TXT) |
|
||||
| `#6010` | `WC_SYS_INIFLG`| 1 | Флаг загрузки INI-конфигурации |
|
||||
| `#6011` | `WC_SYS_RSQBC`| 2 | RSQ буфер-счётчик |
|
||||
| `#6013` | `WC_SYS_RSQB2`| 2 | RSQ буфер 2 |
|
||||
| `#6015` | `WC_SYS_SDBSF`| 1 | Состояние SD1 драйвера |
|
||||
| `#6016` | `WC_SYS_SDBSF2`| 1 | Состояние SD2 драйвера |
|
||||
| `#6017` | `WC_SYS_F3ENTFL`| 1 | Флаг F3/ENTER для файловых операций |
|
||||
| `#6018` | `WC_SYS_FEP2`| 2 | FAT engine page extra |
|
||||
| `#6020` | `WC_SYS_PLRESAD`| 8 | Резидент перехода: A=страница, HL=адрес → JP(HL) |
|
||||
| `#6028` | `WC_SYS_PLRESCALL`| 8 | Резидент вызова: A=страница, HL=адрес → CALL(HL) |
|
||||
| `#8000` | — | — | **Точка входа плагина** (WC делает CALL #8000) |
|
||||
|
||||
Чтение из C:
|
||||
```c
|
||||
#define WC_SYS_ABT (*(volatile uint8_t*)0x6004)
|
||||
#define WC_SYS_TMN (*(volatile uint16_t*)0x6009)
|
||||
#define WC_SYS_HEI (*(volatile uint8_t*)0x600E)
|
||||
|
||||
// Или через макросы wc_api.h:
|
||||
uint8_t h = wc_get_height(); // 25/30/36
|
||||
uint16_t t = wc_get_timer(); // кадровый счётчик
|
||||
uint8_t esc = wc_get_abt(); // 1 = ESC нажат
|
||||
```
|
||||
|
||||
## Вызов API функций
|
||||
|
||||
Все функции WC вызываются через точку доступа #6006:
|
||||
|
||||
```asm
|
||||
WLD EQU #6006
|
||||
|
||||
; Пример вызова функции PRWOW (вывод окна)
|
||||
PRWOW LD A,1
|
||||
JP WLD
|
||||
|
||||
; Пример вызова ESC (проверка нажатия ESC)
|
||||
ESC LD A,#17
|
||||
JP WLD
|
||||
```
|
||||
|
||||
**Регистровые соглашения WC API:**
|
||||
- Вход: **A** = номер функции, остальные параметры в регистрах (см. справочник)
|
||||
- Некоторые функции используют **A'** (альтернативный A через `EX AF,AF'`)
|
||||
- **Портит**: AF, BC, DE, HL, IX
|
||||
- **Сохраняет**: IY, AF', BC', DE', HL', SP
|
||||
|
||||
> ⚠️ **IX сохраняется** диспетчером P8000, но **AF, BC, DE, HL портятся**
|
||||
> при каждом вызове `CALL #6006`.
|
||||
|
||||
---
|
||||
|
||||
## Карта памяти (физические страницы)
|
||||
|
||||
> Источник: `wc_api.h` — полная таблица `WC_PAGE_*`
|
||||
|
||||
| Страницы | Константа | Назначение |
|
||||
|----------|-----------|------------|
|
||||
| `#00` | `WC_PAGE_MTXPG` | Основной текстовый экран (TXT) |
|
||||
| `#01` | `WC_PAGE_FONT0` | Шрифт основного TXT + Hrust/MegaLZ |
|
||||
| `#02` | `WC_PAGE_CODE` | Код WC (ядро) |
|
||||
| `#05` | `WC_PAGE_FAT_ENGINE` | FAT движок + стек + данные |
|
||||
| `#08` | `WC_PAGE_BUFPG` | 2-й текстовый экран / буфер |
|
||||
| `#09` | `WC_PAGE_FONT1` | Шрифт для 2-го TXT |
|
||||
| `#0B–#13` | `WC_PAGE_CAT_LEFT` | Каталог левой панели (9 стр.) |
|
||||
| `#14–#1C` | `WC_PAGE_CAT_RIGHT` | Каталог правой панели (9 стр.) |
|
||||
| `#1D` | `WC_PAGE_RESB` | Буфер сохранения окон (RESB) |
|
||||
| `#1E` | `WC_PAGE_CPPG1` | Позиции левой панели |
|
||||
| `#1F` | `WC_PAGE_CPPG2` | Позиции правой панели |
|
||||
| **#20–#5F** | **`WC_PAGE_TVBPG`** | **Мегабуфер (64 стр. = 1 МБ)**, он же видео: |
|
||||
| `#20–#2F` | `WC_PAGE_GV1` | — видеобуфер 1 (16 стр.) |
|
||||
| `#30–#3F` | `WC_PAGE_GV2` | — видеобуфер 2 |
|
||||
| `#40–#4F` | `WC_PAGE_GV3` | — видеобуфер 3 |
|
||||
| `#50–#5F` | `WC_PAGE_GV4` | — видеобуфер 4 |
|
||||
| `#60` | `WC_PAGE_PLHPG` | Заголовки плагинов |
|
||||
| **#61–#A0** | **`WC_PAGE_PLGPG`** | **Код/данные плагинов (до 64 стр.)** |
|
||||
| `#A1–#BF` | `WC_PAGE_TAP_START` | TAP-область (31 стр. = 496 КБ) |
|
||||
| `#C0–#E7` | `WC_PAGE_VDOS_RAM` | vDOS RAM-диск (40 стр. = 640 КБ) |
|
||||
| `#E8–#EF` | `WC_PAGE_VDOS_FREE` | Свободные страницы |
|
||||
| `#F0` | `WC_PAGE_PNLPG` | Поток левой панели |
|
||||
| `#F1` | `WC_PAGE_PNL_RIGHT` | Поток правой панели |
|
||||
| `#F2` | `WC_PAGE_INIPG` | INI-поток (плагин #00) |
|
||||
| `#F3` | `WC_PAGE_STRPG` | Поток плагина #01 |
|
||||
| `#F4–#F7` | — | SPG loader, boot pages (5,2), panel paths |
|
||||
| `#F8–#FB` | `WC_PAGE_VROM_START` | Виртуальный ROM (4 стр.) |
|
||||
| `#FC–#FF` | — | vDOS: таблицы, драйверы, трапы |
|
||||
|
||||
> **Плагин видит:** свои заказанные страницы (0..N) через `wc_mngc_pl()` /
|
||||
> `wc_mng0_pl()` / `wc_mng8_pl()`. Физическая нумерация скрыта — WC
|
||||
> маппит логическую страницу N плагина в `WC_PAGE_PLGPG + N`.
|
||||
|
||||
---
|
||||
|
||||
## Поддерживаемые устройства хранения
|
||||
|
||||
| Код | Константа | Устройство |
|
||||
|-----|-----------|------------|
|
||||
| 0 | `WC_DEV_SD_ZC` | SD-карта (Z-Controller) |
|
||||
| 1 | `WC_DEV_IDE_NEMO_M` | IDE Nemo — Master |
|
||||
| 2 | `WC_DEV_IDE_NEMO_S` | IDE Nemo — Slave |
|
||||
| 3 | `WC_DEV_SD_NGS` | SD-карта (NeoGS) |
|
||||
| 4 | `WC_DEV_IDE_SMUC_M` | IDE SMUC — Master |
|
||||
| 5 | `WC_DEV_IDE_SMUC_S` | IDE SMUC — Slave |
|
||||
| 6 | `WC_DEV_SD2_ZC` | Вторая SD (Z-Controller SD2) |
|
||||
|
||||
---
|
||||
|
||||
## Работа со страницами памяти
|
||||
|
||||
### Логическая нумерация
|
||||
|
||||
Плагин запрашивает N страниц в заголовке (offset +34). WC выделяет их
|
||||
из пула `#61–#A0`. Плагин обращается по **логическому** номеру 0..N−1.
|
||||
|
||||
### Окна CPU и функции маппинга
|
||||
|
||||
| Окно | Адреса | Функция ASM | Функция C | Примечание |
|
||||
|------|--------|-------------|-----------|------------|
|
||||
| Win 0 | `#0000–#3FFF` | `MNG0_PL` (#4E) | `wc_mng0_pl(page)` | ⚠ Данные для WC API нельзя класть сюда |
|
||||
| Win 2 | `#8000–#BFFF` | `MNG8_PL` (#4F) | `wc_mng8_pl(page)` | Код плагина — обычно стартовая страница |
|
||||
| Win 3 | `#C000–#FFFF` | `MNGC_PL` (#00) | `wc_mngc_pl(page)` | ⚠ Сбрасывается после КАЖДОГО вызова WC API |
|
||||
|
||||
Специальные значения для `MNGC_PL`:
|
||||
- `#FF` — шрифтовая страница (нельзя писать в `#E000–#FFFF`!)
|
||||
- `#FE` — первый текстовый экран (с панелями WC)
|
||||
|
||||
### Резидент перехода (#6020)
|
||||
|
||||
Переключает страницу плагина и делает **безвозвратный** JP:
|
||||
|
||||
```c
|
||||
// C: никогда не вернётся в вызывающий код
|
||||
void wc_resident_jump(uint8_t page, uint16_t addr);
|
||||
```
|
||||
|
||||
```asm
|
||||
; ASM: A = страница (0..N-1), HL = адрес (#8000–#BFFF)
|
||||
LD A,1
|
||||
LD HL,#8000
|
||||
CALL #6020 ; → WC переключает Win2 на стр.1, JP (HL)
|
||||
```
|
||||
|
||||
### Резидент вызова (#6028)
|
||||
|
||||
Вызывает код в другой странице плагина **с возвратом**:
|
||||
|
||||
```c
|
||||
void wc_resident_call(uint8_t page, uint16_t addr);
|
||||
```
|
||||
|
||||
```asm
|
||||
; ASM: A = страница, HL = адрес. Сохраняет всё кроме A,HL.
|
||||
LD A,2
|
||||
LD HL,#A000
|
||||
CALL #6028 ; → WC переключает, CALL (HL), возвращает стр. назад
|
||||
```
|
||||
|
||||
> Добавлено в WC v1.09 (31.07.2024). До этого многостраничные плагины
|
||||
> требовали прямой OUT в порт Page2 (#12AF).
|
||||
|
||||
---
|
||||
|
||||
## Важные замечания
|
||||
|
||||
1. **При входе в плагин может быть любой экран!** Обязательно вызывайте
|
||||
`GEDPL` (#0F) / `wc_gedpl()` для переключения на текстовый режим.
|
||||
|
||||
2. **Вызывать GEDPL лучше после анализа файла**, когда убедились что файл
|
||||
подходит для плагина (и вернули `wc_exit_code = 1` если нет).
|
||||
|
||||
3. **Обязательно сохраняйте IX** при входе в плагин, если планируете
|
||||
использовать файловые функции.
|
||||
|
||||
4. **⚠ Win3 (#C000) сбрасывается после каждого вызова WC API!**
|
||||
Внутри `CALL #6006` WC переключает окно `#C000` на текстовую страницу
|
||||
и **не восстанавливает** его. Если плагин работал через `mngcvpl()` /
|
||||
`mngc_pl()`, он **обязан** повторить маппинг после любого UI-вызова
|
||||
(`gedpl`, `prwow`, `txtpr`, `rresb`, `load512` и т.д.).
|
||||
|
||||
5. **GEDPL затирает #1000–#1447 в окне 0** — нельзя хранить данные в этой области.
|
||||
|
||||
6. **Буферы и строки для файловых функций** (`load512`, `fentry`, `findnext`,
|
||||
и т.д.) **обязаны** быть в `#8000–#FFFF`. WC ремапит `#0000–#7FFF`
|
||||
под FAT-движок внутри каждого файлового вызова.
|
||||
|
||||
7. **FindNext портит #0000–#1FFF** на странице драйвера — не хранить
|
||||
данные там при итерации по каталогу.
|
||||
|
||||
8. **WC API портит регистры:** AF, BC, DE, HL, IX. Сохраняет: IY, AF',
|
||||
BC', DE', HL', SP. Это верно для **всех** функций через `CALL #6006`.
|
||||
|
||||
9. **Стек не настраивается** — плагин использует стек WC (в Win 1, `#4000–#7FFF`).
|
||||
Стек WC достаточно глубокий для обычных плагинов, но рекурсия требует осторожности.
|
||||
|
||||
---
|
||||
|
||||
## Горячие клавиши WC (для пользователя)
|
||||
|
||||
> Источник: `DiskRef/Help.txt`
|
||||
|
||||
| Клавиша | Действие |
|
||||
|---------|----------|
|
||||
| ENTER | Войти в каталог / запустить .WMF / просмотреть файл |
|
||||
| F3 | Встроенный просмотрщик (TXT/HEX/TGA/SCR/MGS, до 1 МБ) |
|
||||
| SHIFT+F3/SHIFT+ENTER | Меню выбора вьювера (плагин с `WC_PCOND_VIEWER`) |
|
||||
| F2 | Пользовательское меню (из `wc.mnu`) |
|
||||
| F4 | Редактирование / выбор текстового редактора |
|
||||
| F5 | Копирование отмеченных файлов в другую панель |
|
||||
| F6 | Переименование файла/каталога |
|
||||
| F7 | Создание каталога |
|
||||
| F8 / DEL | Удаление отмеченных файлов |
|
||||
| F9 | Настройки INI (авто-создаёт каталог WC на загрузочном устройстве) |
|
||||
| F10 | Меню запуска/удаления плагинов |
|
||||
| TAB | Переключение между панелями |
|
||||
| SPACE / INSERT | Отметить файл/каталог |
|
||||
| * | Инвертировать выделение |
|
||||
| ALT+F1/F2 | Выбор диска для левой/правой панели |
|
||||
| CTRL+F3 | Сортировка по имени |
|
||||
| CTRL+F4 | Сортировка по расширению |
|
||||
| CTRL+F6 | Сортировка по размеру |
|
||||
| CTRL+R | Обновить каталог |
|
||||
| CTRL+A | Выделить все объекты |
|
||||
| CTRL+SHIFT | Переключение ENG/RUS |
|
||||
|
||||
---
|
||||
|
||||
## Примеры плагинов
|
||||
|
||||
В каталоге `DiskRef/WC/` находятся скомпилированные плагины WC:
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|------------|
|
||||
| `GSPLAYER.WMF` | Плеер для GS (MP3, MOD, WAV) |
|
||||
| `WPLAYER.WMF` | Музыкальный плеер с плейлистами |
|
||||
| `VGMPLAY.WMF` | VGM плеер (OPL3/AY/SAA) — этот проект |
|
||||
| `TXTEDIT.WMF` | Текстовый редактор с подсветкой синтаксиса |
|
||||
| `BMPV.WMF` | Просмотрщик BMP |
|
||||
| `GIFVIEW.WMF` | Просмотрщик GIF |
|
||||
| `ILBMVIEW.WMF` | Просмотрщик IFF/ILBM |
|
||||
| `FTVIEW.WMF` | Просмотрщик FT-шрифтов |
|
||||
| `MOUNTER.WMF` | Монтирование TRD/TAP/FDI образов |
|
||||
| `ROM_PROG.WMF` | Программатор ROM/Flash |
|
||||
| `SCRSAVER.WMF` | Скринсейвер (тип вызова `WC_PCOND_TIMER`) |
|
||||
| `CLOCK.WMF` | Часы |
|
||||
| `SETIME.WMF` | Установка времени |
|
||||
| `COMPILER.WMF` | Компилятор |
|
||||
| `RE.WMF` | Ассемблер/отладчик |
|
||||
|
||||
---
|
||||
|
||||
## Ссылки на другие разделы документации
|
||||
|
||||
- [02_Plugin_Header_Structure.md](02_Plugin_Header_Structure.md) — формат 512-байтного заголовка
|
||||
- [03_WC_API_Reference.md](03_WC_API_Reference.md) — полный справочник функций
|
||||
- [04_TSConfig_Platform.md](04_TSConfig_Platform.md) — аппаратная платформа, порты, регистры
|
||||
- [06_Example_Plugin.md](06_Example_Plugin.md) — примеры плагинов на ASM и SDCC C
|
||||
@@ -0,0 +1,311 @@
|
||||
# Структура заголовка плагина (512 байт)
|
||||
|
||||
> Источники: `inc/wc_api.h` (константы `WC_PLUGIN_*`, `WC_PCOND_*`),
|
||||
> `DiskRef/WC_History.txt` (история изменений формата)
|
||||
|
||||
## Общая структура
|
||||
|
||||
Заголовок плагина имеет фиксированный размер **512 байт** и содержит всю
|
||||
необходимую информацию для загрузки и запуска плагина системой WC.
|
||||
|
||||
**Текущая версия формата:** `#10` (`WC_PLUGIN_VERSION`), идентификатор: `WildCommanderMDL`.
|
||||
|
||||
Формат неоднократно менялся (WC v0.56, v1.00, v1.02, v1.06, v1.09) —
|
||||
старые плагины несовместимы с новой версией.
|
||||
|
||||
## Детальная структура
|
||||
|
||||
| Offset | Размер | Описание |
|
||||
|--------|--------|----------|
|
||||
| +0 | 16 | Резерв (reserved) |
|
||||
| +16 | 16 | Идентификатор: "WildCommanderMDL" |
|
||||
| +32 | 1 | Версия формата (#10) |
|
||||
| +33 | 1 | Резерв |
|
||||
| +34 | 1 | Количество страниц для плагина |
|
||||
| +35 | 1 | Номер страницы для #8000 (от 0) |
|
||||
| +36 | 12 | Блоки данных (6 x 2 байта) |
|
||||
| +48 | 15 | Резерв |
|
||||
| +63 | 1 | Флаги для блока расширений |
|
||||
| +64 | 96 | Блок расширений (32 x 3 байта) |
|
||||
| +160 | 1 | #00 (маркер окончания расширений) |
|
||||
| +161 | 4 | Максимальный размер файла |
|
||||
| +165 | 32 | Имя плагина |
|
||||
| +197 | 1 | Тип условий вызова плагина |
|
||||
| +198 | 6 | Текст для F4 или F2 |
|
||||
| +204 | 24 | Текст для меню вьювера |
|
||||
| +228 | 32 | Резерв |
|
||||
| +260 | 252 | Структура параметров INI |
|
||||
|
||||
## Детальное описание полей
|
||||
|
||||
### Количество страниц (+34)
|
||||
|
||||
Указывает, сколько 16 КБ страниц памяти будет выделено плагину.
|
||||
Максимум: **64 страницы** (`WC_PLUGIN_MAX_PAGES`) = 1 МБ.
|
||||
|
||||
Страницы выделяются из пула `#61–#A0` (физические), плагин обращается
|
||||
по логическому номеру 0..N−1 через `wc_mngc_pl()` / `wc_mng0_pl()` / `wc_mng8_pl()`.
|
||||
|
||||
Часть страниц может использоваться как буфер, который **сохраняется
|
||||
между повторными запусками** плагина (NEXT/PREV) — код плагина WC не
|
||||
перезагружает, если плагин уже загружен.
|
||||
|
||||
### Номер стартовой страницы (+35)
|
||||
|
||||
Номер страницы (от 0), которая будет включена в окно #8000 при запуске плагина. Нумерация начинается с 0.
|
||||
|
||||
### Блоки данных (+36, 12 байт)
|
||||
|
||||
Описывает до 6 блоков данных плагина:
|
||||
|
||||
```
|
||||
Для каждого блока (2 байта):
|
||||
Байт 0: Номер страницы (от 0) для размещения блока
|
||||
Байт 1: Размер блока (x512 байт)
|
||||
```
|
||||
|
||||
Блоки данных следуют сразу за заголовком плагина (512 байт).
|
||||
|
||||
Пример:
|
||||
```asm
|
||||
DB 0,16 ; Блок 0: код плагина, 16x512 = 8КБ
|
||||
DB 1,32 ; Блок 1: данные плеера, 32x512 = 16КБ
|
||||
DB 2,9 ; Блок 2: дополнительный плеер, 9x512 = 4.5КБ
|
||||
DB 0,0 ; Остальные блоки не используются
|
||||
DB 0,0
|
||||
DB 0,0
|
||||
```
|
||||
|
||||
### Флаги блока расширений (+63)
|
||||
|
||||
Определяет, какая клавиша запускает плагин по расширению:
|
||||
|
||||
```
|
||||
BIT 0: Режим запуска
|
||||
0 - запуск и по ENTER, и по F3
|
||||
1 - см. BIT 1
|
||||
|
||||
BIT 1 (если BIT 0 = 1):
|
||||
0 - запуск только по ENTER
|
||||
1 - запуск только по F3
|
||||
```
|
||||
|
||||
| Биты [1:0] | Поведение |
|
||||
|------------|------------------------------------------|
|
||||
| `00` | ENTER и F3 — оба запускают плагин |
|
||||
| `01` | Только ENTER (F3 = встроенный вьювер WC) |
|
||||
| `11` | Только F3 (ENTER = вход в файл как данные) |
|
||||
|
||||
> Разделение ENTER и F3 добавлено в WC v1.00 (октябрь 2019).
|
||||
|
||||
### Блок расширений (+64, 96 байт)
|
||||
|
||||
Содержит до 32 расширений файлов (по 3 байта каждое).
|
||||
|
||||
**Важно:** Расширения указываются **ТОЛЬКО БОЛЬШИМИ БУКВАМИ!**
|
||||
|
||||
Примеры:
|
||||
```asm
|
||||
DB "TXT" ; Текстовые файлы
|
||||
DB "MOD" ; MOD музыка
|
||||
DB "WAV" ; WAV аудио
|
||||
DB "MP3" ; MP3 аудио
|
||||
DB "VGM" ; VGM музыка
|
||||
; ... до 32 расширений
|
||||
DS 27*3 ; Остальные заполняем нулями
|
||||
```
|
||||
|
||||
Блок можно забить нулями, если не нужна инициация по расширениям.
|
||||
|
||||
### Максимальный размер файла (+161, 4 байта)
|
||||
|
||||
Определяет максимальный размер файла, который плагин может обработать. Файлы больше этого размера плагину не передаются.
|
||||
|
||||
Примеры:
|
||||
```asm
|
||||
DW #FFFF,#FFFF ; Без ограничений
|
||||
DW #6000,#0000 ; Максимум 24КБ
|
||||
```
|
||||
|
||||
### Имя плагина (+165, 32 байта)
|
||||
|
||||
Текстовое имя плагина, которое показывается в меню.
|
||||
|
||||
```asm
|
||||
DB "VGM Player for OPL3 v1.0 "
|
||||
```
|
||||
|
||||
### Тип условий вызова (+197)
|
||||
|
||||
Определяет, при каких условиях плагин должен вызываться (помимо расширений):
|
||||
|
||||
| Код | Константа (C) | Условие вызова |
|
||||
|-----|---------------|----------------|
|
||||
| #00 | `WC_PCOND_EXT_ONLY` | Только по расширению |
|
||||
| #01 | `WC_PCOND_ON_LOAD` | Сразу после загрузки (при ENTER) |
|
||||
| #02 | `WC_PCOND_TIMER` | По таймеру скринсейвера + из меню F10 |
|
||||
| #03 | `WC_PCOND_F10` | Из меню F10 |
|
||||
| #04 | `WC_PCOND_VIEWER` | Из меню вьювера (SHIFT+F3) |
|
||||
| #05 | `WC_PCOND_F10_VIEW` | F10 + SHIFT+F3 |
|
||||
| #11 | `WC_PCOND_F10_F2` | F10 + F2 |
|
||||
| #12 | `WC_PCOND_F2` | По F2 |
|
||||
| #13 | `WC_PCOND_F10_F4` | F10 + F4 |
|
||||
| #14 | `WC_PCOND_F4` | По F4 |
|
||||
| #15 | `WC_PCOND_F10_V_F4` | F10 + SHIFT+F3 + F4 |
|
||||
|
||||
**Примечание:** Все плагины с указанными расширениями также вызываются
|
||||
по расширению. Тип вызова расширяет набор способов запуска.
|
||||
|
||||
### Текст для F2/F4 (+198, 6 байт)
|
||||
|
||||
Если тип вызова #11/#12 - текст для подсказки F2
|
||||
Если тип вызова #13/#14/#15 - текст для подсказки F4
|
||||
|
||||
```asm
|
||||
DB "Edit " ; Для редактора
|
||||
DB "Play " ; Для плеера
|
||||
```
|
||||
|
||||
### Текст для меню вьювера (+204, 24 байта)
|
||||
|
||||
Текст, который выводится в меню выбора вьювера (при типах #04/#05/#15):
|
||||
|
||||
```asm
|
||||
DB "Play VGM on OPL3 "
|
||||
```
|
||||
|
||||
## Структура параметров INI (+260, 252 байта)
|
||||
|
||||
> Добавлено в WC v1.09 (31.07.2024)
|
||||
|
||||
Позволяет плагину принимать параметры из INI-файла WC (`wc.ini`).
|
||||
Параметры отображаются в меню F9 → настройки плагина.
|
||||
|
||||
### Формат записи
|
||||
|
||||
```
|
||||
+0: 1 байт - длина ПОЛНОЙ структуры (включая этот байт) или #00 (конец)
|
||||
+1: 1 байт - default_option (1-based номер опции по умолчанию)
|
||||
+2: N байт - "parameter_name" (ASCII, без терминатора)
|
||||
+2+N: 1 байт - количество опций (1-10)
|
||||
+3+N: M байт - "option1",0, "option2",0, ...
|
||||
... следующая запись или #00
|
||||
```
|
||||
|
||||
Максимум 8 параметров, каждый до 10 опций.
|
||||
|
||||
### Пример
|
||||
|
||||
```asm
|
||||
PAR1:
|
||||
DB PAR1END-PAR1 ; Длина структуры
|
||||
DB 1 ; Опция по умолчанию = 1
|
||||
DB "opl_port" ; Имя параметра
|
||||
DB 2 ; Количество опций
|
||||
DB "C4",0 ; Опция 1
|
||||
DB "C0",0 ; Опция 2
|
||||
PAR1END:
|
||||
PAR2:
|
||||
DB PAR2END-PAR2
|
||||
DB 1
|
||||
DB "freq"
|
||||
DB 3
|
||||
DB "683",0
|
||||
DB "1367",0
|
||||
DB "2734",0
|
||||
PAR2END:
|
||||
DB 0 ; Конец списка параметров
|
||||
```
|
||||
|
||||
### Чтение параметров в плагине
|
||||
|
||||
```c
|
||||
// wc_prm_pl(param_num) — param_num с 0
|
||||
// Возвращает: номер опции (1-based), 0xFF если параметр не задан
|
||||
uint8_t opt = wc_prm_pl(0); // прочитать 1-й параметр
|
||||
if (opt == 0xFF) opt = 1; // значение по умолчанию
|
||||
```
|
||||
|
||||
```asm
|
||||
; ASM: A' = номер параметра (0-based)
|
||||
EXA
|
||||
LD A,0 ; Параметр #0
|
||||
CALL WLD
|
||||
EXA
|
||||
LD A,#53 ; PRM_PL
|
||||
CALL WLD
|
||||
; Z=не задан, NZ: A=номер опции (1-based)
|
||||
```
|
||||
|
||||
В INI-файле (`wc.ini`) параметры записываются по длинному имени плагина:
|
||||
```ini
|
||||
[VGM Player v0.5]
|
||||
opl_port=C4
|
||||
freq=683
|
||||
```
|
||||
|
||||
## Пример полного заголовка
|
||||
|
||||
```asm
|
||||
ORG #6000
|
||||
|
||||
; +0: Резерв
|
||||
DS 16
|
||||
|
||||
; +16: Идентификатор
|
||||
DB "WildCommanderMDL"
|
||||
|
||||
; +32: Версия
|
||||
DB #10
|
||||
|
||||
; +33: Резерв
|
||||
DB 0
|
||||
|
||||
; +34: Количество страниц
|
||||
DB 2
|
||||
|
||||
; +35: Стартовая страница
|
||||
DB 0
|
||||
|
||||
; +36: Блоки данных
|
||||
DB 0,8 ; Код: 4КБ
|
||||
DB 1,16 ; Данные: 8КБ
|
||||
DB 0,0
|
||||
DB 0,0
|
||||
DB 0,0
|
||||
DB 0,0
|
||||
|
||||
; +48: Резерв
|
||||
DS 15
|
||||
|
||||
; +63: Флаги расширений
|
||||
DB 0
|
||||
|
||||
; +64: Расширения
|
||||
DB "VGM"
|
||||
DB "VGZ"
|
||||
DS 30*3
|
||||
|
||||
; +160: Конец расширений
|
||||
DB 0
|
||||
|
||||
; +161: Максимальный размер
|
||||
DW #FFFF,#FFFF
|
||||
|
||||
; +165: Имя плагина
|
||||
DB "VGM Player v1.0 "
|
||||
|
||||
; +197: Тип вызова
|
||||
DB #00 ; По расширению
|
||||
|
||||
; +198-228: Резерв/меню
|
||||
DS 6
|
||||
DS 24
|
||||
DS 32
|
||||
|
||||
; +260: Параметры INI
|
||||
DB 0 ; Нет параметров
|
||||
|
||||
ORG #8000,#6200
|
||||
; Здесь начинается код плагина
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,658 @@
|
||||
# TSConfig Platform Reference
|
||||
|
||||
> Источники: `inc/tsconf.h` (532 стр.), `inc/wc_api.h` (системные константы),
|
||||
> [TSConfig Wiki](https://github.com/tslabs/zx-evo)
|
||||
|
||||
## Обзор платформы
|
||||
|
||||
TS-Configuration (TSConfig) — расширенная конфигурация на базе **ZX Evolution**
|
||||
(Большой Спектрум / ZX Evo), реализованная в FPGA (Altera/Xilinx).
|
||||
|
||||
| Параметр | Значение |
|
||||
|-----------|----------|
|
||||
| CPU | Z80 @ 3.5 / 7 / 14 / 28 МГц |
|
||||
| RAM | до 4096 КБ (256 страниц × 16 КБ) |
|
||||
| ROM | до 512 КБ (32 страницы) |
|
||||
| Видео | до 360×288, 256 цветов, TXT 80/90 столбцов |
|
||||
| Спрайты | До 85 на строку, 8×8–64×64, 3 плоскости |
|
||||
| Тайлы | 2 плоскости, 8×8 |
|
||||
| DMA | Аппаратный контроллер (RAM↔RAM, RAM↔SPI/IDE, FILL, CRAM, SFILE) |
|
||||
| Кэш | 512 байт, по-оконно |
|
||||
| Звук | AY-3-8910 (TurboSound: 2 чипа), OPL3 (YMF262), SAA1099 ×2, HUS |
|
||||
| Хранилище | SD (Z-Controller, NeoGS), IDE (NEMO, SMUC), FAT32 |
|
||||
| Совместимость | Pentagon-128/512/1024, порт #7FFD |
|
||||
|
||||
## Память
|
||||
|
||||
### Страничная адресация
|
||||
|
||||
Процессор Z80 адресует 64 КБ памяти, разделенные на 4 окна по 16 КБ:
|
||||
|
||||
| Окно | Адреса CPU | Регистр страницы | R/W | Reset |
|
||||
|------|------------|------------------|-----|-------|
|
||||
| 0 | #0000..#3FFF | Page0 | W | #00 |
|
||||
| 1 | #4000..#7FFF | Page1 | W | #05 |
|
||||
| 2 | #8000..#BFFF | Page2 | R/W | #02 |
|
||||
| 3 | #C000..#FFFF | Page3 | R/W | #00 |
|
||||
|
||||
**Доступно:**
|
||||
- RAM: до 4096 КБ (256 страниц по 16 КБ)
|
||||
- ROM: до 512 КБ (32 страницы по 16 КБ)
|
||||
|
||||
### Регистры управления памятью
|
||||
|
||||
Доступ к регистрам через порты **#nnAF**, где nn - номер регистра.
|
||||
|
||||
#### MemConfig (регистр #07)
|
||||
|
||||
```
|
||||
Биты:
|
||||
7:6 - LCK128[1:0] - режим порта #7FFD
|
||||
5:4 - резерв
|
||||
3 - W0_RAM - выбор ROM/RAM в окне 0
|
||||
2 - !W0_MAP - режим маппинга окна 0
|
||||
1 - W0_WE - разрешение записи в окно 0
|
||||
0 - ROM128 - выбор страниц маппинга
|
||||
```
|
||||
|
||||
#### Page0..3 (регистры #10-#13)
|
||||
|
||||
8-битные регистры, содержащие номер страницы для соответствующего окна.
|
||||
|
||||
## Порты ввода-вывода
|
||||
|
||||
### Схема адресации портов
|
||||
|
||||
TSConfig использует порты с адресом **#nnAF**, где:
|
||||
- Младшие 8 бит (A0-A7) = #AF
|
||||
- Старшие 8 бит (A8-A15) = номер регистра (nn)
|
||||
|
||||
### Базовые регистры TSConfig
|
||||
|
||||
> Источник: `src_sdcc/inc/tsconf.h` — `#define TSCONF_PORT(reg) (((uint16_t)(reg) << 8) | 0xAF)`
|
||||
|
||||
| Порт | Константа | R/W | Описание |
|
||||
|---------|----------------------------|-----|-------------------------------------|
|
||||
| `#00AF` | `TSCONF_PORT_VCONFIG` | RW | VConfig: видеорежим (VM, RRES) |
|
||||
| `#01AF` | `TSCONF_PORT_VPAGE` | W | VPage: страница видеобуфера |
|
||||
| `#02AF` | `TSCONF_PORT_GXOFFSL` | W | GFX X offset (low) |
|
||||
| `#03AF` | `TSCONF_PORT_GXOFFSH` | W | GFX X offset (high) |
|
||||
| `#04AF` | `TSCONF_PORT_GYOFFSL` | W | GFX Y offset (low) |
|
||||
| `#05AF` | `TSCONF_PORT_GYOFFSH` | W | GFX Y offset (high) |
|
||||
| `#06AF` | `TSCONF_PORT_TSCONFIG` | W | TSConfig: tile/sprite engine |
|
||||
| `#07AF` | `TSCONF_PORT_PALSEL` | W | PalSel: выбор палитры |
|
||||
| `#0FAF` | `TSCONF_PORT_BORDER` | W | Border: цвет бордюра (биты 2:0) |
|
||||
| `#10AF` | `TSCONF_PORT_PAGE0` | **W** | **Page0: страница окна #0000** |
|
||||
| `#11AF` | `TSCONF_PORT_PAGE1` | **W** | **Page1: страница окна #4000** |
|
||||
| `#12AF` | `TSCONF_PORT_PAGE2` | **RW** | **Page2: страница окна #8000** |
|
||||
| `#13AF` | `TSCONF_PORT_PAGE3` | **RW** | **Page3: страница окна #C000** |
|
||||
| `#14AF` | `TSCONF_PORT_FMADDR` | W | Font Maps |
|
||||
| `#15AF` | `TSCONF_PORT_TMPAGE` | W | TileMap page |
|
||||
| `#16AF` | `TSCONF_PORT_T0GPAGE` | W | Tile plane 0 gfx page |
|
||||
| `#17AF` | `TSCONF_PORT_T1GPAGE` | W | Tile plane 1 gfx page |
|
||||
| `#18AF` | `TSCONF_PORT_SGPAGE` | W | Sprite gfx page |
|
||||
| `#20AF` | `TSCONF_PORT_SYSCONFIG` | W | **SysConfig: CPU freq + кэш** |
|
||||
| `#21AF` | `TSCONF_PORT_MEMCONFIG` | W | MemConfig: режим памяти |
|
||||
| `#22AF` | `TSCONF_PORT_HSINT` | W | FRAME-INT: горизонт. позиция |
|
||||
| `#23AF` | `TSCONF_PORT_VSINTL` | W | FRAME-INT: вертик. позиция (low) |
|
||||
| `#24AF` | `TSCONF_PORT_VSINTH` | W | FRAME-INT: вертик. позиция (high) + auto-inc |
|
||||
| `#29AF` | `TSCONF_PORT_FDDVIRT` | W | FDD виртуализация |
|
||||
| `#2AAF` | `TSCONF_PORT_INTMASK` | W | **INTMask: маска прерываний** |
|
||||
| `#2BAF` | `TSCONF_PORT_CACHECONFIG` | W | **CacheConfig: кэш по окнам** |
|
||||
|
||||
#### Порты тайловых смещений
|
||||
|
||||
| Порт | Описание |
|
||||
|------|----------|
|
||||
| `#40AF`/`#41AF` | Tile plane 0: X offset (lo/hi) |
|
||||
| `#42AF`/`#43AF` | Tile plane 0: Y offset (lo/hi) |
|
||||
| `#44AF`/`#45AF` | Tile plane 1: X offset (lo/hi) |
|
||||
| `#46AF`/`#47AF` | Tile plane 1: Y offset (lo/hi) |
|
||||
|
||||
#### Порты DMA
|
||||
|
||||
| Порт | Константа | Описание |
|
||||
|------|-----------|----------|
|
||||
| `#19AF` | `TSCONF_PORT_DMASADDRL` | Source addr low |
|
||||
| `#1AAF` | `TSCONF_PORT_DMASADDRH` | Source addr high |
|
||||
| `#1BAF` | `TSCONF_PORT_DMASADDRX` | Source page (extended) |
|
||||
| `#1CAF` | `TSCONF_PORT_DMADADDRL` | Dest addr low |
|
||||
| `#1DAF` | `TSCONF_PORT_DMADADDRH` | Dest addr high |
|
||||
| `#1EAF` | `TSCONF_PORT_DMADADDRX` | Dest page (extended) |
|
||||
| `#25AF` | `TSCONF_PORT_DMAWPDEV` | Wait Port device |
|
||||
| `#26AF` | `TSCONF_PORT_DMALEN` | Burst length |
|
||||
| `#27AF` | `TSCONF_PORT_DMACTRL` | Control (W) / Status (R) |
|
||||
| `#28AF` | `TSCONF_PORT_DMANUM` | Bursts num (low) |
|
||||
| `#2CAF` | `TSCONF_PORT_DMANUMH` | Bursts num (high) |
|
||||
| `#2DAF` | `TSCONF_PORT_DMAWPADDR` | Wait Port addr |
|
||||
|
||||
> ⚠️ **Частая ошибка:** Page0–Page3 = `#10AF`–`#13AF`, SysConfig = `#20AF`.
|
||||
> Не путать с VConfig (`#00AF`) или VPage (`#01AF`)!
|
||||
|
||||
### Порты OPL3 — YMF262
|
||||
|
||||
**OPL3 чип располагается по портам #C4–#C7:**
|
||||
|
||||
| Порт | Константа | Назначение |
|
||||
|-------|-----------|------------|
|
||||
| `#C4` | `TSCONF_PORT_OPL3_ADDR0` | Bank 0: регистр адреса |
|
||||
| `#C5` | `TSCONF_PORT_OPL3_DATA0` | Bank 0: данные |
|
||||
| `#C6` | `TSCONF_PORT_OPL3_ADDR1` | Bank 1: регистр адреса |
|
||||
| `#C7` | `TSCONF_PORT_OPL3_DATA1` | Bank 1: данные |
|
||||
|
||||
**Регистр NEW (Bank 1, reg 0x05):**
|
||||
- `NEW=1` (0x01) → OPL3 mode: биты L/R в регистрах `C0–C8` управляют маршрутизацией звука
|
||||
- `NEW=0` (0x00) → OPL2-compat: L+R всегда активны (обязательно для файлов OPL2/OPL1!)
|
||||
|
||||
```c
|
||||
// inc/opl3.h
|
||||
void opl3_write_b0(uint8_t reg, uint8_t data); // Bank 0 (#C4/#C5)
|
||||
void opl3_write_b1(uint8_t reg, uint8_t data); // Bank 1 (#C6/#C7)
|
||||
void opl3_init(void); // сброс + NEW=1
|
||||
|
||||
// Для OPL2/OPL1 VGM: после opl3_init() нужно:
|
||||
opl3_write_b1(0x05, 0x00); // NEW=0 — иначе тишина!
|
||||
```
|
||||
|
||||
### Порты AY-3-8910 / YM2149
|
||||
|
||||
| Порт | Константа | Назначение |
|
||||
|---------|-----------|------------|
|
||||
| `#FFFD` | `TSCONF_PORT_AY_ADDR` | Регистр адреса / чтение (`OUT/IN`) |
|
||||
| `#BFFD` | `TSCONF_PORT_AY_DATA` | Запись данных |
|
||||
|
||||
**TurboSound (два AY):** второй чип выбирается через порт `#FFFD`:
|
||||
- `OUT (#FFFD), #FF` (= `TSCONF_AY_TS_CHIP0`) → чип 0 (основной)
|
||||
- `OUT (#FFFD), #FE` (= `TSCONF_AY_TS_CHIP1`) → чип 1
|
||||
|
||||
**Частоты AY** (управление через `wc_turbopl(WC_TURBO_AY, param)`):
|
||||
|
||||
| param | Частота | Примечание |
|
||||
|-------|---------|------------|
|
||||
| 0 | 1,750,000 Гц | Стандарт TSConfig / Pentagon |
|
||||
| 1 | 1,773,400 Гц | NTSC-кварц (315/88 МГц ÷ 2) |
|
||||
| 2 | 3,500,000 Гц | Удвоенная |
|
||||
| 3 | 3,546,800 Гц | Удвоенная NTSC |
|
||||
|
||||
### Порты SAA1099 — MultiSound card
|
||||
|
||||
| Чип | Данные | Адрес | Константы |
|
||||
|------|--------|-------|-----------|
|
||||
| SAA1 | `#00FF` | `#01FF` | `TSCONF_PORT_SAA_DATA/ADDR` |
|
||||
| SAA2 | `#00FE` | `#01FE` | `TSCONF_PORT_SAA2_DATA/ADDR` |
|
||||
|
||||
> Некоторые источники указывают SAA2 по портам `#02FF`/`#03FF` — это
|
||||
> зависит от реализации MultiSound платы. В `tsconf.h` используются
|
||||
> `#00FE`/`#01FE`.
|
||||
|
||||
**Пример записи в регистр OPL3:**
|
||||
```asm
|
||||
; Запись в регистр банка 0
|
||||
LD BC,#C4 ; Порт адреса
|
||||
LD A,reg ; Номер регистра
|
||||
OUT (C),A
|
||||
LD B,#C5 ; Порт данных
|
||||
LD A,value ; Значение
|
||||
OUT (C),A
|
||||
|
||||
; Запись в регистр банка 1
|
||||
LD BC,#C6 ; Порт адреса банка 1
|
||||
LD A,reg ; Номер регистра
|
||||
OUT (C),A
|
||||
LD B,#C7 ; Порт данных банка 1
|
||||
LD A,value ; Значение
|
||||
OUT (C),A
|
||||
```
|
||||
|
||||
## Процессор
|
||||
|
||||
### Частоты CPU
|
||||
|
||||
| Режим | Частота | SysConfig [1:0] | Константа |
|
||||
|--------|---------|-----------------|----------|
|
||||
| 00 | 3.5 МГц | `00` | `TSCONF_CPU_3_5MHZ` |
|
||||
| 01 | 7.0 МГц | `01` | `TSCONF_CPU_7MHZ` |
|
||||
| 10 | 14.0 МГц | `10` | `TSCONF_CPU_14MHZ` |
|
||||
| 11 | 28.0 МГц | `11` | (non-standard, не все FPGA) |
|
||||
|
||||
> В WC API `wc_turbopl(WC_TURBO_CPU, param)` поддерживает
|
||||
> param 0–3 (3.5/7/14/28 МГц).
|
||||
|
||||
Управление через регистр **SysConfig #20AF** (биты 1:0):
|
||||
|
||||
```c
|
||||
// tsconf.h
|
||||
#define TSCONF_CPU_3_5MHZ 0x00
|
||||
#define TSCONF_CPU_7MHZ 0x01
|
||||
#define TSCONF_CPU_14MHZ 0x02
|
||||
#define TSCONF_CPU_MASK 0x03
|
||||
#define TSCONF_CACHE_EN 0x04 // бит 2 — глобальный кэш
|
||||
|
||||
// Convenience macros:
|
||||
tsconf_set_cpu(TSCONF_CPU_14MHZ); // только CPU
|
||||
tsconf_set_cpu_cache(TSCONF_CPU_14MHZ, TSCONF_CACHE_EN); // CPU + кэш
|
||||
```
|
||||
|
||||
### Кэш
|
||||
|
||||
512 байт кэш-памяти для ускорения работы на 14/28 МГц.
|
||||
|
||||
**Глобально:** бит 2 регистра **SysConfig** (`TSCONF_CACHE_EN`).
|
||||
|
||||
**По-оконно:** регистр **CacheConfig #2BAF**:
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_CACHE_EN_0000` | 0x01 | Кэш для #0000–#3FFF |
|
||||
| `TSCONF_CACHE_EN_4000` | 0x02 | Кэш для #4000–#7FFF |
|
||||
| `TSCONF_CACHE_EN_8000` | 0x04 | Кэш для #8000–#BFFF |
|
||||
| `TSCONF_CACHE_EN_C000` | 0x08 | Кэш для #C000–#FFFF |
|
||||
| `TSCONF_CACHE_ALL` | 0x0F | Все окна |
|
||||
| `TSCONF_CACHE_NONE` | 0x00 | Кэш выключен |
|
||||
|
||||
```c
|
||||
tsconf_set_cache(TSCONF_CACHE_EN_8000 | TSCONF_CACHE_EN_C000);
|
||||
```
|
||||
|
||||
**Важно:** После DMA операций необходима **инвалидация кэша**:
|
||||
|
||||
```asm
|
||||
; Инвалидация: 512 байт LDIR на себя
|
||||
LD HL,#FE00
|
||||
LD DE,#FE00
|
||||
LD BC,#0200
|
||||
LDIR
|
||||
```
|
||||
|
||||
## Прерывания
|
||||
|
||||
### Источники прерываний
|
||||
|
||||
TSConfig поддерживает несколько источников INT:
|
||||
|
||||
| Источник | Бит | IM2 вектор | Константа |
|
||||
|----------|-----|------------|-----------|
|
||||
| FRAME | 0 | `#FF` (`TSCONF_IVEC_FRAME`) | `TSCONF_INT_FRAME` |
|
||||
| LINE | 1 | `#FD` (`TSCONF_IVEC_LINE`) | `TSCONF_INT_LINE` |
|
||||
| DMA | 2 | `#FB` (`TSCONF_IVEC_DMA`) | `TSCONF_INT_DMA` |
|
||||
| WTP | 3 | — | `TSCONF_INT_WTP` |
|
||||
|
||||
### Регистр маски прерываний (INTMask #2AAF)
|
||||
|
||||
```
|
||||
Биты:
|
||||
3 - WTP (Wait Port) interrupt enable
|
||||
2 - DMA interrupt enable
|
||||
1 - LINE interrupt enable
|
||||
0 - FRAME interrupt enable
|
||||
|
||||
Reset: #01 (только FRAME включен)
|
||||
```
|
||||
|
||||
```c
|
||||
tsconf_set_intmask(TSCONF_INT_FRAME | TSCONF_INT_LINE);
|
||||
```
|
||||
|
||||
### Позиционирование FRAME-INT
|
||||
|
||||
```c
|
||||
// Задать вертикальную строку и горизонтальную позицию
|
||||
tsconf_set_frame_int(vline, hpos);
|
||||
// Отключить
|
||||
tsconf_disable_frame_int();
|
||||
|
||||
// Авто-инкремент VCNT (для множественных INT в кадре):
|
||||
// VSINTH[7:4] = auto-increment value
|
||||
#define TSCONF_VSINTH_INC(n) ((n) << 4)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MemConfig (#21AF) — Конфигурация памяти
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_MEM_ROM128` | 0x01 | Выбор банка ROM (= бит 4 порта #7FFD) |
|
||||
| `TSCONF_MEM_W0_WE` | 0x02 | Разрешить запись в окно #0000 |
|
||||
| `TSCONF_MEM_W0_MAP_DIS` | 0x04 | Отключить маппинг через Page0 |
|
||||
| `TSCONF_MEM_W0_RAM` | 0x08 | RAM вместо ROM в окне #0000 |
|
||||
| `TSCONF_MEM_LCK128_512K` | 0x00 | Pentagon-512 режим |
|
||||
| `TSCONF_MEM_LCK128_128K` | 0x40 | Pentagon-128 режим |
|
||||
| `TSCONF_MEM_LCK128_AUTO` | 0x80 | Авто-определение |
|
||||
| `TSCONF_MEM_LCK128_1M` | 0xC0 | Расширенный 1 МБ |
|
||||
|
||||
---
|
||||
|
||||
## TSU — Tile/Sprite Engine (#06AF)
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_TS_EXT` | 0x01 | TSU 360×288 (extended) |
|
||||
| `TSCONF_T0Z_EN` | 0x04 | Tile[0] visible (plane 0) |
|
||||
| `TSCONF_T1Z_EN` | 0x08 | Tile[0] visible (plane 1) |
|
||||
| `TSCONF_T0_EN` | 0x20 | Tile layer 0 enable |
|
||||
| `TSCONF_T1_EN` | 0x40 | Tile layer 1 enable |
|
||||
| `TSCONF_S_EN` | 0x80 | Sprite layers enable |
|
||||
|
||||
---
|
||||
|
||||
## HUS — Hurricane Sound Engine
|
||||
|
||||
> Порты `#80AF`–`#87AF` (есть не на всех FPGA-прошивках)
|
||||
|
||||
HUS — аппаратный звуковой движок TSConfig для воспроизведения
|
||||
PCM-сэмплов из RAM с DMA.
|
||||
|
||||
| Порт | Константа | Описание |
|
||||
|------|-----------|----------|
|
||||
| `#80AF` | `TSCONF_PORT_HUSCTRL` | Control (W) / Status (R) |
|
||||
| `#81AF` | `TSCONF_PORT_HUSSAMPRATE` | Sample Rate: 1,750,000 / (SR+1) |
|
||||
| `#82AF` | `TSCONF_PORT_HUSTICKRATEL` | Tick Rate (low) |
|
||||
| `#83AF` | `TSCONF_PORT_HUSTICKRATEH` | Tick Rate (high) |
|
||||
| `#84AF`–`#87AF` | `TSCONF_PORT_HUSRLD0`–`3` | Reload ch 0–31 |
|
||||
|
||||
**Биты статуса (чтение #80AF):**
|
||||
|
||||
| Константа | Бит | Описание |
|
||||
|-----------|-----|----------|
|
||||
| `TSCONF_HUS_ACT` | 0 | 1 = HUS активен |
|
||||
| `TSCONF_HUS_TICK` | 1 | 1 = tick event |
|
||||
| `TSCONF_HUS_HALF` | 6 | 1 = полбуфера |
|
||||
| `TSCONF_HUS_EMPTY` | 7 | 1 = буфер пуст |
|
||||
|
||||
**Управление (запись #80AF):**
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_HUS_PAUSE` | 0x02 | Пауза воспроизведения |
|
||||
| `TSCONF_HUS_RESET` | 0x80 | Сброс HUS |
|
||||
|
||||
### ISR плагина — собственный таймер
|
||||
|
||||
Плагин может зарегистрировать собственный ISR через
|
||||
`wc_int_pl(WC_INT_PLUGIN)` + `wc_int_pl_handler(addr)`.
|
||||
|
||||
В VGM-плеере ISR настраивается через **перехват** вектора IM2
|
||||
(FRAME INT, вектор `#5BFF`) с высокой частотой вызова.
|
||||
POS_TABLE задаёт позиции INT-триггеров в кадре:
|
||||
|
||||
| Вариант | ISR_FREQ | T-states/тик | Тиков/кадр | VGM_SAMPLE_SHIFT | Семплов/тик |
|
||||
|---------|----------|-------------|------------|------------------|---------|
|
||||
| Quarter | **683 Гц** | 5120 T | 14 | 6 (44100/64) | 64 |
|
||||
| Half | 1367 Гц | 2560 T | 28 | 5 (44100/32) | 32 |
|
||||
| Full | 2734 Гц | 1280 T | 56 | 4 (44100/16) | 16 |
|
||||
|
||||
Конфигурация варианта хранится в `inc/variant_cfg.h`:
|
||||
|
||||
```c
|
||||
// variant_cfg.h (текущий вариант)
|
||||
#define ISR_FREQ 683
|
||||
#define ISR_TICKS_PER_FRAME 14 // 683/50≈14
|
||||
#define VGM_SAMPLE_SHIFT 6 // 44100 >> 6 = 689 ≈ 683
|
||||
#define VGM_SAMPLE_MASK 0x3Fu
|
||||
#define VGM_FILL_CMD_BUDGET 32 // команд за один yield
|
||||
#define VARIANT_POS_ENTRIES 14 // записей в pos_table
|
||||
#define VARIANT_POS_STEP 5120 // T-states между INT
|
||||
```
|
||||
|
||||
### Механизм pos_table
|
||||
|
||||
`pos_table` — кольцевой буфер позиций INT в кадре.
|
||||
Каждая запись: 5 байт `[VSINTH, VSINTL, HSINT, next_ptr_lo, next_ptr_hi]`.
|
||||
ISR выводит 3 байта через OUTI (порты VSINTH/VSINTL/HSINT),
|
||||
затем переходит по next_ptr к следующей записи.
|
||||
Генерируется скриптом `scripts/gen_pos_table.js`.
|
||||
|
||||
### Частота прерываний TSConfig (FRAME)
|
||||
|
||||
- FRAME: зависит от видео режима
|
||||
- 50 Гц (312 строк PAL)
|
||||
- 60 Гц (262 строки NTSC)
|
||||
- ~48.8 Гц (320 строк Pentagon)
|
||||
|
||||
## DMA Контроллер
|
||||
|
||||
TSConfig имеет аппаратный DMA для быстрого копирования/заполнения данных.
|
||||
|
||||
### Режимы DMA (регистр DMACTRL #27AF)
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_DMA_RAM_RAM` | 0x09 | RAM → RAM копирование |
|
||||
| `TSCONF_DMA_BLT1_RAM` | 0x89 | RAM → RAM с пропуском нулей (transparency) |
|
||||
| `TSCONF_DMA_BLT2_RAM` | 0x31 | Blitter с aдером |
|
||||
| `TSCONF_DMA_FILL` | 0x21 | Заполнение RAM |
|
||||
| `TSCONF_DMA_RAM_CRAM` | 0xA1 | RAM → CRAM (палитра, RGB555) |
|
||||
| `TSCONF_DMA_RAM_SFILE` | — | RAM → Sprite File |
|
||||
| `TSCONF_DMA_SPI_RAM` | 0x11 | SPI → RAM (чтение SD) |
|
||||
| `TSCONF_DMA_RAM_SPI` | 0x91 | RAM → SPI (запись SD) |
|
||||
| `TSCONF_DMA_IDE_RAM` | 0x19 | IDE → RAM |
|
||||
| `TSCONF_DMA_RAM_IDE` | 0x99 | RAM → IDE |
|
||||
| `TSCONF_DMA_FDD_RAM` | 0x29 | FDD → RAM |
|
||||
| `TSCONF_DMA_RAM_WTP` | 0xB9 | RAM → Wait Port |
|
||||
| `TSCONF_DMA_WTP_RAM` | 0x39 | Wait Port → RAM |
|
||||
|
||||
### Флаги DMA
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_DMA_ASZ_256` | 0x00 | Выравнивание 256 байт |
|
||||
| `TSCONF_DMA_ASZ_512` | 0x04 | Выравнивание 512 байт |
|
||||
| `TSCONF_DMA_D_ALGN` | 0x08 | Выравнивание приёмника |
|
||||
| `TSCONF_DMA_S_ALGN` | 0x10 | Выравнивание источника |
|
||||
| `TSCONF_DMA_ACT` | 0x80 | DMA active (бит статуса при чтении) |
|
||||
|
||||
### Wait Port Device (#25AF)
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_DMAWP_GLUCLOCK` | 0x00 | GluClock |
|
||||
| `TSCONF_DMAWP_COMPORT` | 0x01 | COM-порт |
|
||||
|
||||
### Основные регистры DMA
|
||||
|
||||
> Порты DMA описаны выше в таблице портов. Основные:
|
||||
|
||||
| Регистр | Порт | Описание |
|
||||
|---------|------|----------|
|
||||
| DMA_SAL/H/X | `#19AF`/`#1AAF`/`#1BAF` | Адрес + страница источника |
|
||||
| DMA_DAL/H/X | `#1CAF`/`#1DAF`/`#1EAF` | Адрес + страница приёмника |
|
||||
| DMA_LEN | `#26AF` | Размер бёрста (N) |
|
||||
| DMA_NUM/H | `#28AF`/`#2CAF` | Количество бёрстов (T) |
|
||||
| DMA_CTRL | `#27AF` | Режим + запуск (W) / статус (R) |
|
||||
|
||||
### Пример DMA через WC API (C)
|
||||
|
||||
```c
|
||||
// Копирование 4 КБ из стр. 0 плагина (#8000) в стр. 1 (#8000)
|
||||
wc_dmapl(WC_DMA_INIT_SD); // потребует B,C,HL,DE в регистрах
|
||||
wc_dmapl(WC_DMA_SET_TN); // B=бёрсты, C=размер бёрста
|
||||
wc_dmapl(WC_DMA_RUN_RAM); // запуск RAM→RAM с ожиданием
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Использование портов из SDCC
|
||||
|
||||
`tsconf.h` объявляет все порты как `__sfr __banked` переменные для
|
||||
прямого ввода/вывода без `__asm`:
|
||||
|
||||
```c
|
||||
// Примеры:
|
||||
sfr_page2 = 5; // OUT (#12AF), 5
|
||||
sfr_sysconfig = TSCONF_CPU_14MHZ; // OUT (#20AF), 2
|
||||
sfr_border = 2; // OUT (#0FAF), 2
|
||||
sfr_opl3_addr0 = 0x20; // OUT (#00C4), 0x20
|
||||
sfr_opl3_data0 = 0xFF; // OUT (#00C5), 0xFF
|
||||
|
||||
// Чтение:
|
||||
uint8_t page = sfr_page2; // IN A,(#12AF)
|
||||
```
|
||||
|
||||
Полный список: `sfr_vconfig`, `sfr_vpage`, `sfr_gxoffsl/h`, `sfr_gyoffsl/h`,
|
||||
`sfr_tsconfig`, `sfr_palsel`, `sfr_border`, `sfr_page0`–`sfr_page3`,
|
||||
`sfr_sysconfig`, `sfr_memconfig`, `sfr_intmask`, `sfr_cacheconfig`,
|
||||
`sfr_dmasaddrh/l/x`, `sfr_dmadaddrh/l/x`, `sfr_dmalen`, `sfr_dmactrl`,
|
||||
`sfr_dmanum/h`, `sfr_opl3_addr0/data0/addr1/data1`, `sfr_ay_addr/data`,
|
||||
`sfr_saa_addr/data`, `sfr_saa2_addr/data`.
|
||||
|
||||
## Видео система
|
||||
|
||||
### Видео режимы (VConfig #00AF)
|
||||
|
||||
| Режим | Константа | Разрешения | Цвета |
|
||||
|-------|-----------|------------|-------|
|
||||
| ZX | `TSCONF_VM_ZX` (0x00) | 256×192 | 16 (атрибуты) |
|
||||
| 16c | `TSCONF_VM_16C` (0x01) | 256..360 | 16 (4 bpp) |
|
||||
| 256c | `TSCONF_VM_256C` (0x02) | 256..360 | 256 (8 bpp) |
|
||||
| TXT | `TSCONF_VM_TXT` (0x03) | 80×25, 80×30, 90×36 | 256 |
|
||||
|
||||
**Разрешения:**
|
||||
|
||||
| Константа | Значение | Пиксели |
|
||||
|-----------|----------|---------|
|
||||
| `TSCONF_RRES_256x192` | 0x00 | 256×192 |
|
||||
| `TSCONF_RRES_320x200` | 0x40 | 320×200 |
|
||||
| `TSCONF_RRES_320x240` | 0x80 | 320×240 |
|
||||
| `TSCONF_RRES_360x288` | 0xC0 | 360×288 |
|
||||
|
||||
**Прочие биты VConfig:**
|
||||
|
||||
| Константа | Бит | Описание |
|
||||
|-----------|-----|----------|
|
||||
| `TSCONF_VC_FT_EN` | 2 | FT812 enable (VDAC2) |
|
||||
| `TSCONF_VC_GFXOVR` | 3 | GFX over TSU |
|
||||
| `TSCONF_VC_NOTSU` | 4 | Отключить TSU graphics |
|
||||
| `TSCONF_VC_NOGFX` | 5 | Отключить main graphics |
|
||||
|
||||
**Чтение VConfig (статус):**
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_VDVER_2BIT_PWM` | 0x00 | 2-бит ЦАП (ШИМ) |
|
||||
| `TSCONF_VDVER_3BIT` | 0x01 | 3-бит |
|
||||
| `TSCONF_VDVER_4BIT` | 0x02 | 4-бит |
|
||||
| `TSCONF_VDVER_5BIT` | 0x03 | 5-бит |
|
||||
| `TSCONF_VDVER_VDAC2` | 0x07 | VDAC2 / FT812 |
|
||||
| `TSCONF_STATUS_PWR_UP` | 0x40 | Після включення |
|
||||
|
||||
### Палитра
|
||||
|
||||
256 ячеек палитры, формат RGB555 (15 бит цвета).
|
||||
|
||||
### Спрайты
|
||||
|
||||
- До 85 спрайтов на строку
|
||||
- Размеры: 8x8 до 64x64 пикселей
|
||||
- До 3 плоскостей спрайтов
|
||||
- До 16 палитр на строку
|
||||
|
||||
## Совместимость с Pentagon-128
|
||||
|
||||
### Порт #7FFD
|
||||
|
||||
```
|
||||
Биты:
|
||||
7:6 - дополнительные биты страницы (512K/1024K)
|
||||
5 - LOCK (блокировка переключения)
|
||||
4 - ROM128 (выбор ROM)
|
||||
3 - SCR (экран 5 или 7)
|
||||
2:0 - номер страницы для #C000
|
||||
```
|
||||
|
||||
### Режимы совместимости (MemConfig [7:6])
|
||||
|
||||
| LCK128 | Режим | Описание |
|
||||
|--------|-------|----------|
|
||||
| 00 | Pentagon-512 | `TSCONF_MEM_LCK128_512K` |
|
||||
| 01 | Pentagon-128 | `TSCONF_MEM_LCK128_128K` |
|
||||
| 10 | Авто | `TSCONF_MEM_LCK128_AUTO` |
|
||||
| 11 | 1024 КБ | `TSCONF_MEM_LCK128_1M` |
|
||||
|
||||
---
|
||||
|
||||
## FDD виртуализация (#29AF)
|
||||
|
||||
TSConfig позволяет виртуализировать FDD-приводы (для TRD/TAP):
|
||||
|
||||
| Константа | Значение | Описание |
|
||||
|-----------|----------|----------|
|
||||
| `TSCONF_FDD_A` | 0x01 | Привод A |
|
||||
| `TSCONF_FDD_B` | 0x02 | Привод B |
|
||||
| `TSCONF_FDD_C` | 0x04 | Привод C |
|
||||
| `TSCONF_FDD_D` | 0x08 | Привод D |
|
||||
| `TSCONF_FDD_VG_OPEN` | 0x80 | Открыть VG93 |
|
||||
|
||||
---
|
||||
|
||||
## Файловая система
|
||||
|
||||
WC работает через FAT32:
|
||||
- **SD-карты**: Z-Controller (основной), NeoGS SD
|
||||
- **IDE**: NEMO (Master/Slave), SMUC (Master/Slave)
|
||||
- **Длинные имена**: до 255 символов (с WC v0.73)
|
||||
- **Каталоги**: >4095 записей (с WC v1.05)
|
||||
|
||||
---
|
||||
|
||||
## Полезные ссылки
|
||||
|
||||
- TSConfig спецификация: https://github.com/tslabs/zx-evo
|
||||
- Регистры TSConfig: [src_sdcc/inc/tsconf.h](../src_sdcc/inc/tsconf.h)
|
||||
- WC API: [src_sdcc/inc/wc_api.h](../src_sdcc/inc/wc_api.h)
|
||||
- ISR конфигурация: [src_sdcc/inc/variant_cfg.h](../src_sdcc/inc/variant_cfg.h)
|
||||
|
||||
## Пример инициализации для плагина
|
||||
|
||||
```asm
|
||||
; Базовая инициализация для музыкального плагина
|
||||
|
||||
INIT_PLUGIN:
|
||||
; Сохранить текущее состояние
|
||||
LD A,(#6002) ; Страница в окне 2
|
||||
PUSH AF
|
||||
LD A,(#6003) ; Страница в окне 3
|
||||
PUSH AF
|
||||
|
||||
; Восстановить TXT режим
|
||||
LD A,#0F
|
||||
CALL WLD ; GEDPL
|
||||
|
||||
; Установить частоту CPU
|
||||
LD B,#00 ; Z80 frequency
|
||||
LD C,%01 ; 7 MHz
|
||||
LD A,#0E
|
||||
CALL WLD ; TURBOPL
|
||||
|
||||
; Настроить прерывания
|
||||
LD HL,INT_HANDLER
|
||||
EXA
|
||||
LD A,#FF
|
||||
CALL WLD ; INT_PL
|
||||
EXA
|
||||
|
||||
; Инициализация OPL3
|
||||
CALL INIT_OPL3
|
||||
|
||||
RET
|
||||
|
||||
; Обработчик прерываний
|
||||
INT_HANDLER:
|
||||
PUSH AF, BC, DE, HL
|
||||
; Ваш код обработки музыки
|
||||
CALL UPDATE_MUSIC
|
||||
POP HL, DE, BC, AF
|
||||
EI
|
||||
RETI
|
||||
|
||||
; Инициализация OPL3
|
||||
INIT_OPL3:
|
||||
; Сброс OPL3
|
||||
LD BC,#C4
|
||||
XOR A
|
||||
OUT (C),A
|
||||
LD B,#C5
|
||||
OUT (C),A
|
||||
; Дополнительная инициализация...
|
||||
RET
|
||||
```
|
||||
@@ -0,0 +1,293 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,475 @@
|
||||
# Пример простого плагина для WC
|
||||
|
||||
## Минимальный плагин "Hello World"
|
||||
|
||||
Этот пример показывает базовую структуру плагина для Wild Commander.
|
||||
|
||||
```asm
|
||||
;---------------------------------------
|
||||
; Hello World Plugin
|
||||
; Простейший пример плагина для WC
|
||||
;---------------------------------------
|
||||
|
||||
ORG #6000
|
||||
|
||||
; Константы WC API
|
||||
WLD EQU #6006 ; Точка вызова API
|
||||
ABT EQU #6004 ; Флаг ESC
|
||||
ENT EQU #6005 ; Флаг ENTER
|
||||
|
||||
;---------------------------------------
|
||||
; ЗАГОЛОВОК ПЛАГИНА (512 байт)
|
||||
;---------------------------------------
|
||||
|
||||
; +0: Резерв (16 байт)
|
||||
DS 16
|
||||
|
||||
; +16: Идентификатор (16 байт)
|
||||
DB "WildCommanderMDL"
|
||||
|
||||
; +32: Версия формата
|
||||
DB #10
|
||||
|
||||
; +33: Резерв
|
||||
DB 0
|
||||
|
||||
; +34: Количество страниц
|
||||
DB 1 ; Плагин занимает 1 страницу
|
||||
|
||||
; +35: Стартовая страница
|
||||
DB 0 ; Запускаться будет со страницы 0
|
||||
|
||||
; +36: Блоки данных (6 x 2 байта)
|
||||
DB 0,1 ; Блок 0: код, 1x512 байт
|
||||
DB 0,0
|
||||
DB 0,0
|
||||
DB 0,0
|
||||
DB 0,0
|
||||
DB 0,0
|
||||
|
||||
; +48: Резерв (15 байт)
|
||||
DS 15
|
||||
|
||||
; +63: Флаги расширений
|
||||
DB 0 ; Стандартный режим
|
||||
|
||||
; +64: Блок расширений (32 x 3 байта)
|
||||
DS 32*3 ; Не используем расширения
|
||||
|
||||
; +160: Конец блока расширений
|
||||
DB 0
|
||||
|
||||
; +161: Максимальный размер файла (4 байта)
|
||||
DW #FFFF,#FFFF ; Без ограничений
|
||||
|
||||
; +165: Имя плагина (32 байта)
|
||||
DB "Hello World Plugin "
|
||||
|
||||
; +197: Тип условий вызова
|
||||
DB #03 ; Из меню (F10)
|
||||
|
||||
; +198: Текст для F2/F4 (6 байт)
|
||||
DS 6
|
||||
|
||||
; +204: Текст для меню вьювера (24 байта)
|
||||
DS 24
|
||||
|
||||
; +228: Резерв (32 байта)
|
||||
DS 32
|
||||
|
||||
; +260: Параметры INI (252 байта)
|
||||
DB 0 ; Нет параметров
|
||||
DS 251
|
||||
|
||||
;---------------------------------------
|
||||
; КОД ПЛАГИНА
|
||||
;---------------------------------------
|
||||
|
||||
ORG #8000,#6200
|
||||
|
||||
;---------------------------------------
|
||||
; Точка входа плагина
|
||||
;---------------------------------------
|
||||
PLUGIN:
|
||||
; Сохранить входные параметры
|
||||
LD (SAVE_IX),IX
|
||||
LD (SAVE_HL),HL
|
||||
LD (SAVE_DE),DE
|
||||
LD (SAVE_BC),BC
|
||||
LD (SAVE_A),A
|
||||
|
||||
; Восстановить текстовый режим
|
||||
CALL GEDPL
|
||||
|
||||
; Показать окно
|
||||
LD IX,WINDOW
|
||||
CALL PRWOW
|
||||
|
||||
; Вывести текст
|
||||
LD HL,TEXT
|
||||
LD DE,#0102 ; Y=1, X=2
|
||||
LD BC,12 ; Длина
|
||||
CALL PRSRW
|
||||
|
||||
; Применить цвет
|
||||
EXA
|
||||
LD A,%00001111 ; Синий на белом
|
||||
CALL PRIAT
|
||||
EXA
|
||||
|
||||
; Главный цикл
|
||||
MAIN_LOOP:
|
||||
EI
|
||||
HALT ; Ждем прерывание
|
||||
|
||||
; Проверка ESC
|
||||
CALL ESC_KEY
|
||||
JR Z,MAIN_LOOP
|
||||
|
||||
; Выход
|
||||
LD IX,WINDOW
|
||||
CALL RRESB ; Стереть окно
|
||||
|
||||
XOR A ; A=0: просто выход
|
||||
RET
|
||||
|
||||
;---------------------------------------
|
||||
; WC API функции
|
||||
;---------------------------------------
|
||||
|
||||
PRWOW: ; Вывод окна
|
||||
LD A,1
|
||||
JP WLD
|
||||
|
||||
RRESB: ; Стирание окна
|
||||
LD A,2
|
||||
JP WLD
|
||||
|
||||
PRSRW: ; Печать строки
|
||||
LD A,3
|
||||
JP WLD
|
||||
|
||||
PRIAT: ; Установка цвета
|
||||
EXA
|
||||
LD A,4
|
||||
JP WLD
|
||||
|
||||
ESC_KEY: ; Проверка ESC
|
||||
LD A,#17
|
||||
JP WLD
|
||||
|
||||
GEDPL: ; Восстановление TXT режима
|
||||
LD A,#0F
|
||||
JP WLD
|
||||
|
||||
;---------------------------------------
|
||||
; Данные
|
||||
;---------------------------------------
|
||||
|
||||
; Структура окна
|
||||
WINDOW:
|
||||
DB 0 ; Флаги
|
||||
DB 0 ; Маска курсора
|
||||
DB 255 ; X (центр)
|
||||
DB 255 ; Y (центр)
|
||||
DB 30 ; Ширина
|
||||
DB 5 ; Высота
|
||||
DB %01111111 ; Цвет (белый на синем)
|
||||
DB 0 ; Резерв
|
||||
DW #0000 ; Буфер
|
||||
DB 0,0 ; Разделители
|
||||
|
||||
; Текст
|
||||
TEXT:
|
||||
DB "Hello World!"
|
||||
|
||||
; Сохраненные регистры
|
||||
SAVE_A: DB 0
|
||||
SAVE_BC: DW 0
|
||||
SAVE_DE: DW 0
|
||||
SAVE_HL: DW 0
|
||||
SAVE_IX: DW 0
|
||||
|
||||
END
|
||||
```
|
||||
|
||||
## Компиляция
|
||||
|
||||
Используйте sjasm для компиляции:
|
||||
|
||||
```batch
|
||||
sjasm hello.asm hello.wmf
|
||||
```
|
||||
|
||||
## Пояснения к коду
|
||||
|
||||
### Структура
|
||||
|
||||
1. **Заголовок (#6000-#61FF)** - 512 байт метаданных
|
||||
2. **Код (#8000+)** - исполняемый код плагина
|
||||
|
||||
### Ключевые моменты
|
||||
|
||||
1. **GEDPL обязателен!** Вызывайте его в начале, чтобы восстановить текстовый режим.
|
||||
|
||||
2. **Структура окна** должна быть в памяти плагина (#8000-#BFFF).
|
||||
|
||||
3. **Главный цикл** должен включать:
|
||||
- `EI` - разрешить прерывания
|
||||
- `HALT` - ждать прерывание (экономит энергию)
|
||||
- Проверку клавиш
|
||||
|
||||
4. **Выход** через `RET` с кодом в A.
|
||||
|
||||
## Пример с файлом
|
||||
|
||||
```asm
|
||||
;---------------------------------------
|
||||
; Text Viewer Plugin - просмотр текстовых файлов
|
||||
;---------------------------------------
|
||||
|
||||
ORG #6000
|
||||
|
||||
WLD EQU #6006
|
||||
|
||||
; ... Заголовок плагина ...
|
||||
|
||||
; +64: Расширения
|
||||
DB "TXT" ; .txt файлы
|
||||
DB "DOC" ; .doc файлы
|
||||
DS 30*3
|
||||
|
||||
; +197: Тип вызова
|
||||
DB #00 ; По расширению
|
||||
|
||||
ORG #8000,#6200
|
||||
|
||||
;---------------------------------------
|
||||
; Точка входа
|
||||
;---------------------------------------
|
||||
PLUGIN:
|
||||
; Проверить тип вызова
|
||||
CP #00 ; По расширению?
|
||||
JR Z,VIEW_FILE
|
||||
|
||||
; Другой тип вызова - не наш файл
|
||||
LD A,1 ; Файл не опознан
|
||||
RET
|
||||
|
||||
VIEW_FILE:
|
||||
; Сохранить параметры
|
||||
LD (FILESIZE),HL
|
||||
LD (FILESIZE+2),DE
|
||||
LD (FILENAME),BC
|
||||
LD (PANEL_IX),IX
|
||||
|
||||
; Восстановить TXT режим
|
||||
CALL GEDPL
|
||||
|
||||
; Загрузить начало файла
|
||||
LD HL,BUFFER
|
||||
LD B,16 ; 16 блоков = 8KB
|
||||
CALL LOAD512
|
||||
|
||||
; Показать окно
|
||||
LD IX,WINDOW
|
||||
CALL PRWOW
|
||||
|
||||
; Вывести текст
|
||||
LD HL,BUFFER
|
||||
LD DE,#0101
|
||||
LD BC,1000
|
||||
CALL PRSRW
|
||||
|
||||
; Цикл просмотра
|
||||
VIEW_LOOP:
|
||||
EI
|
||||
HALT
|
||||
|
||||
CALL ESC_KEY
|
||||
JR Z,VIEW_LOOP
|
||||
|
||||
; Выход
|
||||
LD IX,WINDOW
|
||||
CALL RRESB
|
||||
XOR A
|
||||
RET
|
||||
|
||||
;---------------------------------------
|
||||
; WC API
|
||||
;---------------------------------------
|
||||
|
||||
LOAD512: LD A,#30
|
||||
JP WLD
|
||||
|
||||
PRWOW: LD A,1
|
||||
JP WLD
|
||||
|
||||
RRESB: LD A,2
|
||||
JP WLD
|
||||
|
||||
PRSRW: LD A,3
|
||||
JP WLD
|
||||
|
||||
ESC_KEY: LD A,#17
|
||||
JP WLD
|
||||
|
||||
GEDPL: LD A,#0F
|
||||
JP WLD
|
||||
|
||||
;---------------------------------------
|
||||
; Данные
|
||||
;---------------------------------------
|
||||
|
||||
WINDOW:
|
||||
DB 0
|
||||
DB 0
|
||||
DB 0 ; X=0
|
||||
DB 0 ; Y=0
|
||||
DB 80 ; На весь экран
|
||||
DB 25
|
||||
DB %01111111
|
||||
DB 0
|
||||
DW #0000
|
||||
DB 0,0
|
||||
|
||||
BUFFER: DS 8192 ; Буфер для файла
|
||||
|
||||
FILENAME: DW 0
|
||||
FILESIZE: DS 4
|
||||
PANEL_IX: DW 0
|
||||
|
||||
END
|
||||
```
|
||||
|
||||
## Следующий шаг
|
||||
|
||||
Изучите примеры в каталоге PlugRef/:
|
||||
- **wild_player/** - полноценный музыкальный плеер
|
||||
- **gsplayer/** - плеер для GS
|
||||
- **txt_editor/** - редактор текста
|
||||
|
||||
---
|
||||
|
||||
## Минимальный плагин на SDCC C
|
||||
|
||||
> Компилятор: SDCC 4.x, ключи `--opt-code-size -mz80 --sdcccall 1`
|
||||
> Источник соглашений: `src_sdcc/inc/wc_api.h`
|
||||
|
||||
```c
|
||||
/*
|
||||
* hello_plugin.c — минимальный WC-плагин на SDCC C
|
||||
*
|
||||
* Сборка:
|
||||
* sdcc --opt-code-size -mz80 --sdcccall 1 --code-loc 0x8000 \
|
||||
* --data-loc 0xB800 -I inc hello_plugin.c wc_api.s crt0.s
|
||||
* makebin -s 16384 hello_plugin.ihx hello_plugin.bin
|
||||
* pack hello_plugin.bin HELLO.WMF # добавить заголовок WMF
|
||||
*/
|
||||
|
||||
#include "inc/wc_api.h"
|
||||
#include "inc/types.h"
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
* DATA: размещается в #B800+ (--data-loc 0xB800)
|
||||
* ──────────────────────────────────────────────── */
|
||||
|
||||
/* Окно в секции DATA (адрес в #8000–#BFFF — обязательно!) */
|
||||
static wc_window_t g_win;
|
||||
|
||||
/* Буфер вывода */
|
||||
static char g_buf[64];
|
||||
|
||||
/*
|
||||
* wc_exit_code — специальная переменная: WC читает A при
|
||||
* выходе из main(). Значения: 0=ESC, 2=след. файл, 3=reload dir.
|
||||
*/
|
||||
uint8_t wc_exit_code;
|
||||
|
||||
/* ─────────────────────────────────────────────────
|
||||
* CODE: размещается в #8000+ (--code-loc 0x8000)
|
||||
* ──────────────────────────────────────────────── */
|
||||
|
||||
int main(void) {
|
||||
/* 1. Восстановить дисплей WC (TXT-режим, панели)
|
||||
* ⚠ Затирает #1000–#1447 в окне 0 */
|
||||
wc_gedpl();
|
||||
|
||||
/* 2. Настроить окно */
|
||||
g_win.type = WC_WIN_SINGLE; /* однолинейная рамка */
|
||||
g_win.cur_mask = 0x07;
|
||||
g_win.x = 255; /* 255 = по центру */
|
||||
g_win.y = 255;
|
||||
g_win.width = 44;
|
||||
g_win.height = 6;
|
||||
g_win.color = WC_COLOR(WC_BLACK, WC_BRIGHT_WHITE);
|
||||
g_win.buf_addr = 0x0000; /* 0 = автосохранение фона в RESB */
|
||||
|
||||
/* 3. Нарисовать окно */
|
||||
wc_prwow(&g_win);
|
||||
|
||||
/* 4. Вывести текст (разметка WC: 0x0E = центрирование) */
|
||||
wc_txtpr(&g_win, "\x0eHello from C plugin!", 1, 1);
|
||||
wc_txtpr(&g_win, "\x0e\x03Press ESC to exit", 3, 1);
|
||||
|
||||
/* 5. Главный цикл */
|
||||
while (1) {
|
||||
if (wc_key_esc()) break;
|
||||
if (wc_key_enter()) break;
|
||||
|
||||
/* пример: цикл ожидания клавиши без блокировки */
|
||||
/* uint8_t k = wc_kbscn(WC_KBSCN_NORMAL); */
|
||||
}
|
||||
|
||||
/* 6. Убрать окно и восстановить дисплей */
|
||||
wc_rresb(&g_win);
|
||||
wc_gedpl();
|
||||
|
||||
/* 7. Код выхода */
|
||||
wc_exit_code = 0; /* 0 = нормальный выход */
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Полезные паттерны
|
||||
|
||||
**Загрузка файла в мегабуфер:**
|
||||
```c
|
||||
void load_file_to_megabuf(void) {
|
||||
uint8_t page = 0;
|
||||
uint8_t rc;
|
||||
|
||||
wc_stream(WC_STREAM_ROOT); // открыть файловый поток
|
||||
// wc_fentry() уже вызван WC перед стартом плагина
|
||||
|
||||
do {
|
||||
/* ⚠ После любого wc_* Win3 сбрасывается — переключаем снова */
|
||||
wc_mngcvpl(page);
|
||||
rc = wc_load512(0xC000, 32); // читать 16 КБ
|
||||
page++;
|
||||
} while (rc == 0 && page < 64);
|
||||
/* rc == WC_EOF (0x0F) = конец файла, rc != 0 && != 0x0F = ошибка */
|
||||
}
|
||||
```
|
||||
|
||||
**Установка собственного ISR:**
|
||||
```c
|
||||
extern void my_isr(void); // ISR написан на ASM в asm/isr.s
|
||||
|
||||
void setup_isr(void) {
|
||||
wc_int_pl(WC_INT_PLUGIN); // режим плагина
|
||||
wc_int_pl_handler((uint16_t)my_isr); // задать адрес
|
||||
/* Теперь my_isr() вызывается ~1367 раз/сек */
|
||||
}
|
||||
|
||||
void restore_isr(void) {
|
||||
wc_int_pl(WC_INT_DISABLE_ALL); // вернуть управление WC
|
||||
}
|
||||
```
|
||||
|
||||
**Переключение CPU на 14 МГц:**
|
||||
```c
|
||||
wc_turbopl(WC_TURBO_CPU, 0x02); // 14 МГц
|
||||
// ...работа...
|
||||
wc_turbopl(WC_TURBO_RESTORE, 0); // восстановить
|
||||
```
|
||||
@@ -0,0 +1,147 @@
|
||||
# TODO: Потоковая (streaming) распаковка inflate
|
||||
|
||||
**Дата:** 2026-03-28
|
||||
**Статус:** Идея / Анализ завершён, реализация отложена
|
||||
|
||||
## Цель
|
||||
|
||||
Заменить текущую полную предварительную распаковку VGZ → мегабуфер
|
||||
на потоковую (чанковую) распаковку с прерываниями EI, что даст:
|
||||
|
||||
- Мгновенный старт воспроизведения (не ждать 2–10 сек)
|
||||
- Снятие ограничения 1 МБ (мегабуфер) на размер raw VGM
|
||||
- Возможность играть VGZ файлы > 496 КБ (TAP) через кольцевой буфер
|
||||
|
||||
## Ключевые находки
|
||||
|
||||
### 1. ISR не использует Win 0 / Win 1 / Win 3
|
||||
|
||||
ISR-хэндлер и все данные (cmd_buf_a/b, isr_read_ptr, pos_ptr) живут
|
||||
исключительно в Win 2 (0x8000–0xBFFF, _CODE + _DATA). Это означает:
|
||||
|
||||
**Win 0, Win 1, Win 3 можно свободно переключать при EI.**
|
||||
|
||||
### 2. Раскладка окон во время inflate-чанка
|
||||
|
||||
| Win | Адреса | Содержимое | Кто использует |
|
||||
|-----|-----------------|---------------------------|--------------------------|
|
||||
| 0 | 0x0000–0x3FFF | Source VGZ page (ротация) | inflate: чтение HL' |
|
||||
| 1 | 0x4000–0x7FFF | Decode buffer (PAGE_DECODE)| inflate: запись DE |
|
||||
| 2 | 0x8000–0xBFFF | Player code + ISR + DATA | **НЕ ТРОГАЕМ**, ISR тут |
|
||||
| 3 | 0xC000–0xFFFF | Inflate code + Huffman | inflate: исполнение |
|
||||
|
||||
Win 2 сейчас занят decode buffer (#EA) во время inflate.
|
||||
Для streaming нужно перенести decode buffer из Win 2 в Win 1.
|
||||
|
||||
### 3. Self-modifying code = бесплатное сохранение состояния
|
||||
|
||||
Все переменные inflate (`src_page`, `dst_page`, `dst`, `interim_dst`,
|
||||
`this_was_last_block`, Huffman таблицы ~10 КБ) — self-modifying code
|
||||
на inflate-странице (Win 3). При unmap/remap страницы значения
|
||||
автоматически сохраняются. Стек (SP = 0xFFF0, адреса возврата
|
||||
inflate_core → blocktype2 → decode_tableloop) тоже на inflate-странице.
|
||||
|
||||
**Нужно сохранять только CPU-регистры (18 байт):**
|
||||
- AF, BC, DE, HL (8 байт main regs)
|
||||
- AF', BC', DE', HL' (8 байт alt regs — bit-stream state!)
|
||||
- IX (2 байта)
|
||||
- SP хранится в C-переменной caller
|
||||
|
||||
### 4. DMA вместо LDIR для page flush
|
||||
|
||||
Текущий `copy_decoded_page` (LDIR 16 КБ) = **344064 T** ≈ 49 мс @ 7 МГц.
|
||||
|
||||
DMA RAM→RAM (TSCONF_DMA_RAM_RAM = 0x09): **~8000–16000 T** (20–40× быстрее).
|
||||
Адресация: 22-bit [page(8)][H(6)][L(7)][0].
|
||||
DMA копирует страницу→страницу без участия CPU и без маппинга окон.
|
||||
|
||||
Двойной буфер DMA: пока DMA копирует страницу N, inflate пишет в страницу N+1.
|
||||
Не нужно ждать флаг TSCONF_DMA_ACT.
|
||||
|
||||
### 5. Backref: DMA для cross-page, LDIR для intra-page
|
||||
|
||||
~80–90% backrefs внутри текущего буфера → LDIR из Win 1 в Win 1.
|
||||
|
||||
Cross-page backrefs (distance > current_offset): DMA из физической
|
||||
страницы history → физическую страницу decode buffer. Окна не трогаем.
|
||||
|
||||
Исключение: overlapping backrefs (distance < length, паттерн "repeat")
|
||||
DMA не гарантирует побайтную семантику → fallback: temp remap Win 0 = history,
|
||||
LDIR, restore Win 0 = src.
|
||||
|
||||
### 6. Overflow 258 байт — решение для Win 1
|
||||
|
||||
Если decode buffer в Win 1 (0x4000–0x7FFF), overflow LDIR пойдёт
|
||||
в Win 2 (player code). **Нельзя.**
|
||||
|
||||
Решение: split-copy перед LDIR. Проверить DE + BC > 0x8000?
|
||||
Если да — LDIR до границы, DMA flush, сброс DE = 0x4000, LDIR остаток.
|
||||
Overhead: ~30 T на backref (проверка).
|
||||
|
||||
### 7. Yield point: decode_tableloop
|
||||
|
||||
Единственная разумная точка yield — вершина `decode_tableloop`.
|
||||
Добавить yield_counter (self-mod var): dec → jp z, inflate_yield.
|
||||
Caller задаёт количество 8-символьных батчей за чанк.
|
||||
|
||||
### 8. Оценка бюджета
|
||||
|
||||
| Параметр | Значение |
|
||||
|----------------------------------|--------------------------|
|
||||
| Потребление VGM (типичный) | ~2.5 КБ/сек (50 б/кадр) |
|
||||
| Скорость inflate @ 7 МГц | ~50–100 КБ/сек |
|
||||
| Нужный duty cycle | **2.5–5% CPU** |
|
||||
| Overhead контекста (вход+выход) | ~600 T / 71680 T = 0.8% |
|
||||
| Запас | 10–20× |
|
||||
|
||||
### 9. Кольцевой буфер выхода
|
||||
|
||||
```
|
||||
Megabuffer pages: [P0][P1][P2][P3][P4][P5][P6][P7] ... ring
|
||||
|
||||
write_ptr → inflate пишет (producer)
|
||||
read_ptr → player читает (consumer)
|
||||
```
|
||||
|
||||
- Старт воспроизведения после первых 2–4 готовых страниц
|
||||
- Нет ограничения 1 МБ: ring buffer кольцуется
|
||||
- Max VGM raw: 496 КБ × 3 (сжатие) ≈ 1.5 МБ
|
||||
|
||||
## Контекст переключения (pseudo-code)
|
||||
|
||||
```asm
|
||||
inflate_enter:
|
||||
; Save Win 0/1/3 page numbers (from C vars or sys vars)
|
||||
; OUT #10AF = src_page ; Win 0 = source VGZ
|
||||
; OUT #11AF = PAGE_DECODE ; Win 1 = decode buffer
|
||||
; OUT #13AF = inflate_page ; Win 3 = inflate code
|
||||
; Restore saved CPU regs (18 bytes from save area on inflate page)
|
||||
; JP resume_point
|
||||
|
||||
inflate_yield:
|
||||
; Save CPU regs to save area on inflate page
|
||||
; ld sp, (caller_sp)
|
||||
; Restore Win 0/1/3 from saved values
|
||||
; ret to main loop
|
||||
```
|
||||
|
||||
## План реализации
|
||||
|
||||
1. [ ] Перенести decode buffer из Win 2 в Win 1
|
||||
2. [ ] Добавить split-copy проверку для LDIR overflow в Win 1
|
||||
3. [ ] Реализовать yield/resume в decode_tableloop
|
||||
4. [ ] Заменить LDIR page flush на DMA
|
||||
5. [ ] Реализовать DMA backref для cross-page
|
||||
6. [ ] Добавить кольцевой буфер (producer/consumer) в main.c
|
||||
7. [ ] Добавить inflate_enter / inflate_yield в inflate_call.s
|
||||
8. [ ] Тестирование с файлами разных размеров и степеней сжатия
|
||||
|
||||
## Риски
|
||||
|
||||
- makeHuffmanTable: может занять десятки тысяч T-states (непредсказуемо).
|
||||
При yield внутри него нужен глубокий стек save. Альтернатива:
|
||||
не yield'ить во время построения таблиц, только во время decode.
|
||||
- DMA overlap с ISR: ISR не использует DMA, конфликтов нет.
|
||||
- Первый блок может быть dynamic Huffman → долгое построение таблиц перед
|
||||
первыми декодированными данными. Mitigation: буферизовать 2–4 страницы
|
||||
перед стартом воспроизведения.
|
||||
Reference in New Issue
Block a user