Add OPL4 playback and analyzer support
This commit is contained in:
@@ -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