Add OPL4 playback and analyzer support
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
/* Auto-generated by gen_freq_tables.js — DO NOT EDIT */
|
||||
#ifndef FREQ_LUT_MAP_H
|
||||
#define FREQ_LUT_MAP_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* AY/YM2149: bypass |psg_khz - 1750| <= 35 */
|
||||
#define FREQ_LUT_HW_KHZ 1750u
|
||||
#define FREQ_LUT_BYPASS_TOL 35u /* 2% */
|
||||
/* YM2203: bypass |ym_khz - 3500| <= 70 */
|
||||
#define FREQ_LUT_HW_YM_KHZ 3500u
|
||||
#define FREQ_LUT_BYPASS_TOL_YM 70u /* 2% */
|
||||
|
||||
#define FREQ_LUT_AY_COUNT 6u
|
||||
#define FREQ_LUT_YM_COUNT 5u
|
||||
#define FREQ_LUT_PSG_ENTRIES 4096u /* 12-bit PSG */
|
||||
#define FREQ_LUT_PSG_BYTES 8192u /* per PSG table */
|
||||
|
||||
typedef struct {
|
||||
uint16_t clk_khz; /* canonical clock in kHz (PSG or YM master) */
|
||||
uint16_t tol_khz; /* ±tolerance in kHz (5%) */
|
||||
uint8_t page; /* plugin page (PAGE_BASE-based) */
|
||||
uint16_t offset; /* PSG table byte offset within page */
|
||||
} freq_lut_entry_t;
|
||||
|
||||
/* AY/YM2149 PSG table map (tolerance = 5% of canonical PSG clock)
|
||||
* Layout: 2 ratios per page, PSG_A@0x0000, PSG_B@0x2000 */
|
||||
static const freq_lut_entry_t freq_lut_map_ay[FREQ_LUT_AY_COUNT] = {
|
||||
{ 1790u, 90u, 1, 0x0000 }, /* NTSC_1790 */
|
||||
{ 1500u, 75u, 1, 0x2000 }, /* 3MHz_1500 */
|
||||
{ 2000u, 100u, 2, 0x0000 }, /* 4MHz_2000 */
|
||||
{ 1000u, 50u, 2, 0x2000 }, /* CPC_1000 */
|
||||
{ 1250u, 63u, 3, 0x0000 }, /* 1250 */
|
||||
{ 750u, 38u, 3, 0x2000 } /* 1500_750 */
|
||||
};
|
||||
|
||||
/* YM2203 table map (full master clock, PSG table = same as AY entry) */
|
||||
static const freq_lut_entry_t freq_lut_map_ym[FREQ_LUT_YM_COUNT] = {
|
||||
{ 3580u, 179u, 1, 0x0000 }, /* NTSC_1790 (YM2203@3580kHz) */
|
||||
{ 3000u, 150u, 1, 0x2000 }, /* 3MHz_1500 (YM2203@3000kHz) */
|
||||
{ 4000u, 200u, 2, 0x0000 }, /* 4MHz_2000 (YM2203@4000kHz) */
|
||||
{ 2500u, 125u, 3, 0x0000 }, /* 1250 (YM2203@2500kHz) */
|
||||
{ 1500u, 75u, 3, 0x2000 } /* 1500_750 (YM2203@1500kHz) */
|
||||
};
|
||||
|
||||
/* FM F-Number множитель (YM2203): scaled = fnum × fm_mul / 7.
|
||||
* 0 = bypass (масштабирование не нужно).
|
||||
* ASM на Z80: умножение через shifts+adds, деление на 7 binary division */
|
||||
static const uint8_t freq_lut_fm_mul[FREQ_LUT_YM_COUNT] = {
|
||||
0, /* NTSC_1790: 1.022727 = bypass (2.3%) */
|
||||
6, /* 3MHz_1500: 0.857143 = ×6/7 */
|
||||
8, /* 4MHz_2000: 1.142857 = ×8/7 */
|
||||
5, /* 1250: 0.714286 = ×5/7 */
|
||||
3 /* 1500_750: 0.428571 = ×3/7 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Usage in vgm.c (parse_header):
|
||||
*
|
||||
* 1. Bypass check: |clock_khz - HW| <= BYPASS_TOL → native
|
||||
* 2. Find matching table → set page, PSG base = offset
|
||||
* 3. For YM2203: fm_mul = freq_lut_fm_mul[j]
|
||||
*
|
||||
* PSG: scaled_period = freq_lut_base[period_12bit]
|
||||
* FM: asm_scale_fm() : fb_scaled = fnum × fm_mul / 7
|
||||
* if (fb_scaled > 2047) block++, fb_scaled >>= 1
|
||||
*/
|
||||
|
||||
#endif /* FREQ_LUT_MAP_H */
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @file isr.h
|
||||
* @brief ISR (Interrupt Service Routine) для FRAME-прерывания TSConfig
|
||||
* @version 2.0
|
||||
*
|
||||
* Зависимости: types.h.
|
||||
* Реализация: asm/isr.s (чистый ASM, hot-path без C).
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* АРХИТЕКТУРА
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Частота ISR задаётся вариантом сборки (ISR_FREQ в variant_cfg.h).
|
||||
* N_ENTRIES прерываний × STEP T-states = 71680 T = 1 TV-кадр (замыкание).
|
||||
*
|
||||
* Позиции INT — предрассчитанная таблица POS_TABLE (N_ENTRIES x 5 байт).
|
||||
* ISR читает 3 байта через OUTI и записывает в VSINTH/VSINTL/HSINT,
|
||||
* затем переходит по next_ptr (замкнутый цикл).
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ДВОЙНАЯ БУФЕРИЗАЦИЯ КОМАНД
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* CMD_BUF_A, CMD_BUF_B — по 512 байт (до 128 команд).
|
||||
* ISR читает из активного буфера, main loop заполняет свободный.
|
||||
* Переключение буфера: CMD_END_BUF в конце каждого заполненного.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ФОРМАТ КОМАНДЫ (4 байта, выровнено)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Код │ Байт 1 │ Байт 2 │ Байт 3 │ Действие
|
||||
* ──────────────┼────────┼────────┼────────┼─────────────────────
|
||||
* CMD_WRITE_AY │ reg │ val │ 0 │ OUT AY8910 chip 1
|
||||
* CMD_INC_SEC │ 0 │ 0 │ 0 │ Инкр. isr_play_seconds
|
||||
* CMD_WRITE_AY2 │ reg │ val │ 0 │ OUT AY8910 chip 2 (TS)
|
||||
* CMD_CALL_WC │ 0 │ 0 │ 0 │ Вызов WC ISR handler
|
||||
* CMD_WRITE_B0 │ reg │ val │ 0 │ OUT OPL3 Bank 0
|
||||
* CMD_SKIP_TICKS│ N │ smsk_lo│ smsk_hi│ Пропустить N позиций pos_table
|
||||
* CMD_WRITE_SAA │ reg │ val │ 0 │ OUT SAA1099 chip 0
|
||||
* CMD_WRITE_B1 │ reg │ val │ 0 │ OUT OPL3 Bank 1
|
||||
* CMD_WRITE_SA2 │ reg │ val │ 0 │ OUT SAA1099 chip 1
|
||||
* CMD_WRITE_WAVE│ reg │ val │ 0 │ OUT OPL4 Wave-часть (#7E/#7F)
|
||||
* CMD_WAIT │ lo │ hi │ 0 │ Ждать (hi<<8)|lo тиков
|
||||
* CMD_END_BUF │ 0 │ 0 │ 0 │ Переключить буфер
|
||||
*
|
||||
* CMD_SKIP_TICKS carries 16-bit spectrum bitmask: bit N → bar N triggered
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР ИСПОЛЬЗОВАНИЯ
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "isr.h"
|
||||
*
|
||||
* // 1. Заполнить оба буфера
|
||||
* fill_buffer(cmd_buf_a);
|
||||
* fill_buffer(cmd_buf_b);
|
||||
*
|
||||
* // 2. Настроить ISR
|
||||
* isr_enabled = 0; // пока не запускать
|
||||
* isr_active_buf = 0; // начать с буфера A
|
||||
* isr_init(); // IM2 + таблица векторов
|
||||
*
|
||||
* // 3. Запустить
|
||||
* isr_enabled = 1;
|
||||
* __asm__("ei");
|
||||
*
|
||||
* // 4. В main loop:
|
||||
* if (isr_active_buf != last_buf) {
|
||||
* fill_buffer(isr_active_buf ? cmd_buf_a : cmd_buf_b);
|
||||
* last_buf = isr_active_buf;
|
||||
* }
|
||||
*/
|
||||
|
||||
#ifndef ISR_H
|
||||
#define ISR_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ── Коды команд (8 типов, шаг 0x20 + ISR_DONE) ─────────────────── */
|
||||
#define CMD_WRITE_AY 0x00 /* AY8910 chip 1: [reg, val, 0] */
|
||||
#define CMD_INC_SEC 0x10 /* Инкрементировать счётчик секунд: [0,0,0] */
|
||||
#define CMD_WRITE_AY2 0x20 /* AY8910 chip 2 (TurboSound): [reg,val,0]*/
|
||||
#define CMD_CALL_WC 0x30 /* Вызов WC ISR handler: [0,0,0] */
|
||||
#define CMD_WRITE_B0 0x40 /* OPL3 Bank 0 write: [reg, val, 0] */
|
||||
#define CMD_SKIP_TICKS 0x50 /* Пропустить N pos_table: [N,sml,smh] */
|
||||
#define CMD_WRITE_SAA 0x60 /* SAA1099 write: [reg, val, 0] bit7=chip */
|
||||
#define CMD_WRITE_B1 0x80 /* OPL3 Bank 1 write: [reg, val, 0] */
|
||||
#define CMD_WRITE_WAVE 0x70 /* OPL4 Wave-часть write (ZXM-MoonSound, порты #7E/#7F): [reg, val, 0] */
|
||||
#define CMD_WAIT 0xC0 /* Ждать N ISR-тиков: [lo, hi, 0] */
|
||||
#define CMD_END_BUF 0xE0 /* Переключить буфер: [0, 0, 0] */
|
||||
#define CMD_ISR_DONE 0xF0 /* Заморозить ISR, выставить isr_done: [0,0,0] */
|
||||
|
||||
/* ── Размер командного буфера ────────────────────────────────────── */
|
||||
#define CMD_BUF_SIZE 512 /* байт; 128 команд по 4 байта */
|
||||
|
||||
/* ── Параметры варианта (ISR_FREQ, VGM_SAMPLE_*, budget) ───── */
|
||||
#include "variant_cfg.h"
|
||||
|
||||
/* ── Переменные ISR (volatile = разделяемые между ISR и main) ────── */
|
||||
|
||||
/** Управляющий байт MultiSound (пишется в #FFFD перед каждой записью AY).
|
||||
* bit0: chip select (0=chip1, 1=chip2) — ISR управляет автоматически.
|
||||
* bit1: 1=normal read (всегда 1).
|
||||
* bit2: 0=FM ON (high-Z), 1=FM OFF (forced low).
|
||||
* bit3: 0=SAA CLK ON, 1=SAA CLK OFF.
|
||||
* bit[7:4]: 0xF (маркер MultiSound ctrl).
|
||||
* Idle = 0xFE (FM OFF, SAA OFF). */
|
||||
extern volatile uint8_t isr_ms_ctrl;
|
||||
|
||||
/** Индекс активного буфера: 0 = A, 1 = B.
|
||||
* Записывается ISR при CMD_END_BUF, читается main loop. */
|
||||
extern volatile uint8_t isr_active_buf;
|
||||
|
||||
/** Флаг включения: 0 = ISR пропускает выполнение команд
|
||||
* (pos_table и tick_ctr продолжают работать).
|
||||
* Записывается main loop, читается ISR.
|
||||
* instant_abort() использует это для заморозки без доп. флагов. */
|
||||
extern volatile uint8_t isr_enabled;
|
||||
|
||||
/** Счётчик тиков ISR. Инкрементируется каждый ISR-вызов (частота = ISR_FREQ),
|
||||
* оборачивается при 256 (полный оборот ≈ 256/ISR_FREQ секунд).
|
||||
* main loop читает для определения прошедшего времени. */
|
||||
extern volatile uint8_t isr_tick_ctr;
|
||||
|
||||
/** Цвет бордюра, восстанавливаемый ISR при выходе.
|
||||
* Записывается main loop (0=black, 2=red и т.д.), читается ISR. */
|
||||
extern volatile uint8_t isr_border_color;
|
||||
|
||||
/** Счётчик секунд воспроизведения.
|
||||
* Инкрементируется ISR по команде CMD_INC_SEC, читается main loop. */
|
||||
extern volatile uint16_t isr_play_seconds;
|
||||
|
||||
/** Флаг завершения: 1 = ISR заморожен на CMD_ISR_DONE.
|
||||
* Записывается ISR, читается main loop.
|
||||
* Main loop очищает при старте воспроизведения. */
|
||||
extern volatile uint8_t isr_done;
|
||||
|
||||
/** Командные буферы A и B. Заполняются main loop, читаются ISR. */
|
||||
extern uint8_t cmd_buf_a[CMD_BUF_SIZE];
|
||||
extern uint8_t cmd_buf_b[CMD_BUF_SIZE];
|
||||
|
||||
/* ── Spectrum analyzer (16 bars, 0-8 levels) ─────────────────────── */
|
||||
#define SPECTRUM_BARS 16
|
||||
#define SPECTRUM_MAX_LEVEL 8
|
||||
|
||||
/** уровни спектра для каждой из 16 полос, 0..SPECTRUM_MAX_LEVEL.
|
||||
* Заполняются ISR (через CMD_SKIP_TICKS), читаются main loop для рендеринга.
|
||||
* Каждый бит в CMD_SKIP_TICKS соответствует одной полосе, устанавливается при активации.
|
||||
* Например, если бит 0x0001 установлен, то полоса 0 (самая левая) будет на максимальном уровне.
|
||||
* Если бит 0x0002 установлен, то полоса 1 будет на максимальном уровне, и так далее.
|
||||
*/
|
||||
extern volatile uint8_t spectrum_levels[SPECTRUM_BARS];
|
||||
|
||||
/** Контрольный счётчик текущей задержки CMD_WAIT.
|
||||
* ISR декрементирует при каждом тике, переходит к следующей команде после достижения 0.
|
||||
* Не используется напрямую main loop — только ISR. */
|
||||
extern volatile uint16_t isr_wait_ctr;
|
||||
|
||||
/** Указатель чтения ISR внутри активного буфера. */
|
||||
extern volatile uint16_t isr_read_ptr;
|
||||
|
||||
/* ── Функции ─────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Перехват вектора IM2 (#5BFF) и настройка 2734 Hz.
|
||||
*
|
||||
* Действия:
|
||||
* 1. Сохранить вектор WC (#5BFF) → isr_saved_vec
|
||||
* 2. Записать адрес isr_handler в #5BFF
|
||||
* 3. Настроить INTMASK = FRAME only, позиции INT
|
||||
* 4. Инициализировать pos_ptr, wait_ctr, tick_ctr, read_ptr
|
||||
* 5. IM 2 (EI делается вызывающим кодом)
|
||||
*
|
||||
* ВАЖНО: вызывать ПОСЛЕ заполнения обоих буферов!
|
||||
* ВАЖНО: вызывать при DI (прерывания выключены).
|
||||
*/
|
||||
void isr_init(void);
|
||||
|
||||
/**
|
||||
* Восстановить вектор WC и сбросить INT-позиции.
|
||||
*
|
||||
* Действия:
|
||||
* 1. Восстановить оригинальный вектор из isr_saved_vec → #5BFF
|
||||
* 2. Сбросить позицию INT на line=0 (один INT/кадр)
|
||||
* 3. INTMASK = FRAME only (WC default)
|
||||
* НЕ делает EI — вызывающий код решает сам.
|
||||
*
|
||||
* Безопасно вызывать без предшествующего isr_init (no-op).
|
||||
*/
|
||||
void isr_deinit(void);
|
||||
|
||||
/**
|
||||
* Вызов оригинального WC ISR handler из main loop.
|
||||
*
|
||||
* WC ожидает периодический вызов своего ISR (обновление таймера,
|
||||
* клавиатуры, часов). Вызывать ~50 Гц из main loop.
|
||||
* Делает DI → JP в WC handler → WC делает EI+RET.
|
||||
* Безопасно вызывать без предшествующего isr_init (no-op).
|
||||
*/
|
||||
void call_wc_handler(void);
|
||||
|
||||
#endif /* ISR_H */
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @file keys.h
|
||||
* @brief PS/2 клавиатура через TSConfig CMOS FIFO (#EFF7/#DFF7/#BFF7)
|
||||
*
|
||||
* ps2_init() — одноразовая инициализация: открытие PS/2 портов,
|
||||
* активация FIFO, старт FSM в режиме DRAIN.
|
||||
* ps2_poll() — вызывать из главного цикла; rate-limit (~1 раз в TV-кадр).
|
||||
* За один вызов полностью сливает FIFO (tight loop).
|
||||
* Возвращает последний найденный KEY_* или KEY_NONE.
|
||||
*
|
||||
* Единственный бит состояния — skip flag (после F0 пропустить 1 байт).
|
||||
* E0 не требует skip — сами коды не совпадают с нашими, а E0 F0 SC
|
||||
* корректно обрабатывается через F0→skip.
|
||||
*
|
||||
* PS/2 Set 2 скан-коды:
|
||||
* ESC (#76) → KEY_ESC — выход
|
||||
* SPACE (#29) → KEY_NEXT — следующий трек
|
||||
* P (#4D) → KEY_PREV — предыдущий трек
|
||||
* 1 (#16) → KEY_SAA1 — SAA режим 1 (chip0)
|
||||
* 2 (#1E) → KEY_SAA2 — SAA режим 2 (chip1)
|
||||
*/
|
||||
|
||||
#ifndef KEYS_H
|
||||
#define KEYS_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ── Коды клавиш (возврат ps2_poll) ──────────────────────────────────── */
|
||||
#define KEY_NONE 0 /* нет нажатия */
|
||||
#define KEY_ESC 1 /* ESC → выход */
|
||||
#define KEY_NEXT 2 /* SPACE → следующий */
|
||||
#define KEY_PREV 3 /* P → предыдущий */
|
||||
#define KEY_SAA1 4 /* 1 → SAA mode 1 */
|
||||
#define KEY_SAA2 5 /* 2 → SAA mode 2 */
|
||||
#define KEY_SAA3 6 /* 3 → SAA mode 3 */
|
||||
|
||||
/**
|
||||
* Инициализация PS/2: открыть порты, активировать FIFO, слив буфера.
|
||||
* Вызвать один раз при старте плагина (до первого ps2_poll).
|
||||
*/
|
||||
void ps2_init(void);
|
||||
|
||||
/**
|
||||
* Деинициализация PS/2: слив FIFO, деактивация FIFO, отключение портов.
|
||||
* Вызвать при выходе из плагина (после главного цикла).
|
||||
*/
|
||||
void ps2_deinit(void);
|
||||
|
||||
/**
|
||||
* Опросить PS/2 FIFO (rate-limited, ~1 раз в кадр).
|
||||
* Клавиша подтверждается только после полного цикла make + release.
|
||||
* Возвращает KEY_* при подтверждении, KEY_NONE иначе.
|
||||
*/
|
||||
uint8_t ps2_poll(void);
|
||||
|
||||
#endif /* KEYS_H */
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @file opl3.h
|
||||
* @brief OPL3 (YMF262) — константы регистров и функции записи
|
||||
* @version 2.0
|
||||
*
|
||||
* Самодостаточная библиотека: зависит только от types.h.
|
||||
* Реализация — asm/opl3.s (чистый inline OUT).
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПОРТЫ TSConfig ДЛЯ OPL3
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Порт │ Описание
|
||||
* ──────┼────────────────────────────
|
||||
* #C4 │ Bank 0 Address (reg number)
|
||||
* #C5 │ Bank 0 Data (reg value)
|
||||
* #C6 │ Bank 1 Address
|
||||
* #C7 │ Bank 1 Data
|
||||
*
|
||||
* Задержка между записью адреса и данных НЕ нужна при 14 МГц
|
||||
* с wait-states: одна инструкция OUT (C),A занимает достаточно
|
||||
* тактов чтобы YMF262 обработал адрес до прихода данных.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* КАРТА РЕГИСТРОВ OPL3 (Bank 0, то же для Bank 1)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Рег. │ Описание
|
||||
* ───────────┼────────────────────────────────────────
|
||||
* 0x01 │ Wavesel enable (бит 5 = 1)
|
||||
* 0x04 │ Connection select (4-op mode, Bank 0)
|
||||
* 0x05 (B1) │ OPL3 mode enable (бит 0 = 1)
|
||||
* 0x08 │ Note select / CSW (только Bank 0)
|
||||
* 0x20–0x35 │ Operator: AM/VIB/EG type/KSR/MULT
|
||||
* 0x40–0x55 │ Operator: KSL/Total Level (громкость)
|
||||
* 0x60–0x75 │ Operator: Attack Rate/Decay Rate
|
||||
* 0x80–0x95 │ Operator: Sustain Level/Release Rate
|
||||
* 0xA0–0xA8 │ Channel: F-Number low 8 bits
|
||||
* 0xB0–0xB8 │ Channel: Key-On + Octave + F-Number hi
|
||||
* 0xBD │ Percussion mode / AM depth / VIB depth
|
||||
* 0xC0–0xC8 │ Channel: Feedback + Algorithm + Output
|
||||
* 0xE0–0xF5 │ Operator: Waveform select
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "opl3.h"
|
||||
*
|
||||
* void play_note(void) {
|
||||
* opl3_init(); // включить OPL3
|
||||
* opl3_write_b0(0x20, 0x01); // op1: MULT=1
|
||||
* opl3_write_b0(0x40, 0x10); // op1: TL=16
|
||||
* opl3_write_b0(0x60, 0xF0); // op1: AR=F, DR=0
|
||||
* opl3_write_b0(0x80, 0x77); // op1: SL=7, RR=7
|
||||
* opl3_write_b0(0xA0, 0x98); // F-num lo
|
||||
* opl3_write_b0(0xB0, 0x31); // Key-On, Oct=4, F-hi
|
||||
* }
|
||||
*/
|
||||
|
||||
#ifndef OPL3_H
|
||||
#define OPL3_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ── Порты OPL3 на шине TSConfig ─────────────────────────────────── */
|
||||
#define OPL3_ADDR0 0xC4 /* Bank 0 register address */
|
||||
#define OPL3_DATA0 0xC5 /* Bank 0 register data */
|
||||
#define OPL3_ADDR1 0xC6 /* Bank 1 register address */
|
||||
#define OPL3_DATA1 0xC7 /* Bank 1 register data */
|
||||
|
||||
/* ── Нумерация регистров (часто используемые) ────────────────────── */
|
||||
#define OPL3_REG_WAVESEL 0x01 /* Waveform select enable (бит 5) */
|
||||
#define OPL3_REG_CONNSEL 0x04 /* Connection select (4-op, Bank0) */
|
||||
#define OPL3_REG_OPL3EN 0x05 /* OPL3 mode reg (Bank 1 only!) */
|
||||
#define OPL3_OPL3EN_ON 0x01 /* NEW=1: OPL3 mode (L/R via C0-C8)*/
|
||||
#define OPL3_OPL3EN_OFF 0x00 /* NEW=0: OPL2 compat (L+R always) */
|
||||
#define OPL3_REG_CSW 0x08 /* CSW / Note select (Bank 0) */
|
||||
#define OPL3_REG_BD 0xBD /* Percussion / AM / VIB depth */
|
||||
|
||||
/* Key-On бит в регистрах 0xB0-0xB8 */
|
||||
#define OPL3_KEY_ON 0x20
|
||||
|
||||
/* ── Функции (реализация: asm/opl3.s) ────────────────────────────── */
|
||||
|
||||
/** Записать регистр Bank 0: addr -> #C4, data -> #C5 */
|
||||
void opl3_write_b0(uint8_t addr, uint8_t data);
|
||||
|
||||
/** Записать регистр Bank 1: addr -> #C6, data -> #C7 */
|
||||
void opl3_write_b1(uint8_t addr, uint8_t data);
|
||||
|
||||
/**
|
||||
* Инициализация OPL3:
|
||||
* 1. Bank1 reg[0x05] = 0x01 — NEW=1, OPL3 mode (регистры C0-C8 управляют L/R)
|
||||
* 2. Bank0 reg[0x01] = 0x20 — Waveform Select Enable
|
||||
* 3. Полный сброс всех 18 каналов: KeyOff, TL, F-Num (opl3_reset)
|
||||
*
|
||||
* ⚠ После `opl3_init()` чип находится в режиме OPL3 (NEW=1):
|
||||
* - Регистры C0-C8 задают маршрутизацию L/R (биты 4-5)
|
||||
* - OPL2/OPL1 VGM файлы НЕ устанавливают биты L/R → тишина!
|
||||
*
|
||||
* Для OPL1/OPL2 файлов после init вызовите:
|
||||
* opl3_write_b1(OPL3_REG_OPL3EN, 0x00); // NEW=0: OPL2-compat mode
|
||||
* В этом режиме L+R включены автоматически, биты C0-C8[5:4] игнорируются.
|
||||
*/
|
||||
void opl3_init(void);
|
||||
|
||||
/** Заглушить все 18 каналов (KeyOff: B0-B8 = 0, обе банки) */
|
||||
void opl3_reset(void);
|
||||
|
||||
/** AY-3-8910 / YM2149 chip 1: mixer off + volume 0 */
|
||||
void ay_silence(void);
|
||||
|
||||
/** AY-3-8910 chip 2 (TurboSound): select chip2, silence, switch back */
|
||||
void ay2_silence(void);
|
||||
|
||||
/** AY chip 1: write single register */
|
||||
void ay_write_reg(uint8_t reg, uint8_t val);
|
||||
|
||||
/** AY chip 2 (TurboSound): write single register */
|
||||
void ay2_write_reg(uint8_t reg, uint8_t val);
|
||||
|
||||
/** SAA1099 chip 1: reset + disable + zero amplitudes */
|
||||
void saa_silence(void);
|
||||
|
||||
/** SAA1099 chip 2: reset + disable + zero amplitudes */
|
||||
void saa2_silence(void);
|
||||
|
||||
/** SAA1099 chip 1: write single register */
|
||||
void saa_write_reg(uint8_t reg, uint8_t val);
|
||||
|
||||
/** SAA1099 chip 2: write single register */
|
||||
void saa2_write_reg(uint8_t reg, uint8_t val);
|
||||
|
||||
#endif /* OPL3_H */
|
||||
@@ -0,0 +1,531 @@
|
||||
/**
|
||||
* @file tsconf.h
|
||||
* @brief Полный справочник регистров TSConfig (TSL FPGA-ускоритель ZX Spectrum)
|
||||
* @version 2.0
|
||||
*
|
||||
* Самодостаточная библиотека: зависит только от types.h.
|
||||
* Подходит для любого TSConfig-проекта на SDCC Z80.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* АРХИТЕКТУРА ПОРТОВ
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Все порты TSConfig адресуются по схеме: (REG_NUM << 8) | 0xAF
|
||||
* Запись: OUT (C),A где BC = порт.
|
||||
*
|
||||
* На ASM:
|
||||
* LD BC, TSCONF_PORT_SYSCONFIG ; BC = 0x20AF
|
||||
* LD A, TSCONF_CPU_14MHZ
|
||||
* OUT (C), A
|
||||
*
|
||||
* На C:
|
||||
* tsconf_out(TSCONF_PORT_SYSCONFIG, TSCONF_CPU_14MHZ);
|
||||
*
|
||||
* Макросы-обёртки (конец файла) упрощают типичные операции:
|
||||
* tsconf_set_cpu(TSCONF_CPU_14MHZ);
|
||||
* tsconf_set_border(1);
|
||||
* tsconf_set_frame_int(100, 0);
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ТАЙМИНГИ TV-РЕЖИМА (Pentagon-128)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Строк в кадре: 320 (0–319)
|
||||
* Строки 0–31: вертикальное гашение
|
||||
* Строки 32–319: видимая область (бордер + пиксели)
|
||||
*
|
||||
* Пикселей в строке: 448 (7 МГц), = 224 T-states при 3.5 МГц
|
||||
* Пиксели 0–87: горизонтальное гашение
|
||||
* Пиксели 88–447: видимая область
|
||||
*
|
||||
* T-states в кадре: 320 × 224 = 71680 T
|
||||
*
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
* ПРЕРЫВАНИЯ (IM2)
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Три источника, каждый выставляет свой вектор на шину данных:
|
||||
* FRAME (#FF, приоритет 0) — совпадение растровых счётчиков
|
||||
* с регистрами HSINT/VSINT
|
||||
* LINE (#FD, приоритет 1) — каждая строка при HCNT = 0
|
||||
* DMA (#FB, приоритет 2) — конец DMA-транзакции
|
||||
*
|
||||
* HSINT = HCNT[8:1]: горизонтальная позиция (0–223). >223 = выкл.
|
||||
* VSINTL/VSINTH = VCNT[8:0]: вертикальная строка (0–319). >319 = выкл.
|
||||
*
|
||||
* VINT_INC[3:0] в VSINTH: автоинкремент VCNT после каждого FRAME-INT
|
||||
* (позволяет менять строку прерывания без записи в порты).
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ВИДЕО
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* VM[1:0]: 00=ZX (256x192, атрибуты), 01=16c, 10=256c, 11=txt
|
||||
* RRES[1:0]: 00=256x192, 01=320x200, 10=320x240, 11=360x288
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* DMA
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Адрес = 22-бит: [X(8)][H(6)][L(7)][0] — младший бит всегда 0.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР (минимальный)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "tsconf.h"
|
||||
*
|
||||
* void demo(void) {
|
||||
* tsconf_set_cpu(TSCONF_CPU_14MHZ); // 14 МГц
|
||||
* tsconf_set_border(2); // красный бордюр
|
||||
* tsconf_set_vconfig(TSCONF_VM_ZX // ZX-режим
|
||||
* | TSCONF_RRES_256x192); // 256x192
|
||||
* tsconf_set_frame_int(100, 0); // INT на строке 100
|
||||
* tsconf_set_intmask(TSCONF_INT_FRAME); // только FRAME
|
||||
* tsconf_set_cpu(TSCONF_CPU_3_5MHZ); // вернуть 3.5
|
||||
* }
|
||||
*/
|
||||
|
||||
#ifndef TSCONF_H
|
||||
#define TSCONF_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* Макрос формирования адреса порта
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT(reg) (((uint16_t)(reg) << 8) | 0xAF)
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ВИДЕО — VConfig (#00AF)
|
||||
* W: RRES[1:0] | - | NOGFX | NOTSU | GFXOVR* | FT_EN** | VM[1:0]
|
||||
* R: - | PWR_UP | - | FDRVER | VDVER[2:0]
|
||||
* * только VDAC, ** только VDAC2. Сброс: 0x00
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_VCONFIG TSCONF_PORT(0x00)
|
||||
|
||||
/* VM[1:0] — видеорежим */
|
||||
#define TSCONF_VM_ZX 0x00 /* ZX-Spectrum, 256×192, атрибуты */
|
||||
#define TSCONF_VM_16C 0x01 /* 16 цветов, 4 бита/пиксель */
|
||||
#define TSCONF_VM_256C 0x02 /* 256 цветов, 8 бит/пиксель */
|
||||
#define TSCONF_VM_TXT 0x03 /* Текстовый режим */
|
||||
#define TSCONF_VM_MASK 0x03
|
||||
|
||||
/* FT_EN, GFXOVR, NOTSU, NOGFX */
|
||||
#define TSCONF_VC_FT_EN 0x04 /* FT812 enable (VDAC2) */
|
||||
#define TSCONF_VC_GFXOVR 0x08 /* GFX поверх TSU (VDAC) */
|
||||
#define TSCONF_VC_NOTSU 0x10 /* Отключить TSU-графику */
|
||||
#define TSCONF_VC_NOGFX 0x20 /* Отключить основную графику */
|
||||
|
||||
/* RRES[1:0] — разрешение экрана */
|
||||
#define TSCONF_RRES_256x192 (0x00 << 6)
|
||||
#define TSCONF_RRES_320x200 (0x01 << 6)
|
||||
#define TSCONF_RRES_320x240 (0x02 << 6)
|
||||
#define TSCONF_RRES_360x288 (0x03 << 6)
|
||||
#define TSCONF_RRES_MASK (0x03 << 6)
|
||||
|
||||
/* R: Status (то же адрес #00AF, чтение) */
|
||||
#define TSCONF_STATUS_VDVER_MASK 0x07
|
||||
#define TSCONF_STATUS_FDRVER 0x10
|
||||
#define TSCONF_STATUS_PWR_UP 0x40
|
||||
#define TSCONF_VDVER_2BIT_PWM 0x00
|
||||
#define TSCONF_VDVER_3BIT 0x01
|
||||
#define TSCONF_VDVER_4BIT 0x02
|
||||
#define TSCONF_VDVER_5BIT 0x03
|
||||
#define TSCONF_VDVER_VDAC2 0x07
|
||||
|
||||
/* ── VPage (#01AF): адрес видеостраницы RA[21:14] ─────────────────── */
|
||||
#define TSCONF_PORT_VPAGE TSCONF_PORT(0x01)
|
||||
|
||||
/* ── GXOffsL (#02AF), GXOffsH (#03AF): X-смещение графики ─────────── */
|
||||
#define TSCONF_PORT_GXOFFSL TSCONF_PORT(0x02) /* XO[7:0] */
|
||||
#define TSCONF_PORT_GXOFFSH TSCONF_PORT(0x03) /* бит 0 = XO[8] */
|
||||
|
||||
/* ── GYOffsL (#04AF), GYOffsH (#05AF): Y-смещение графики ─────────── */
|
||||
#define TSCONF_PORT_GYOFFSL TSCONF_PORT(0x04) /* YO[7:0] */
|
||||
#define TSCONF_PORT_GYOFFSH TSCONF_PORT(0x05) /* бит 0 = YO[8] */
|
||||
|
||||
/* ── TSConfig (#06AF): включение tile/sprite движка ────────────────── */
|
||||
#define TSCONF_PORT_TSCONFIG TSCONF_PORT(0x06)
|
||||
#define TSCONF_TS_EXT 0x01 /* TSU 360×288 (не ZX-Evo) */
|
||||
#define TSCONF_T0Z_EN 0x04 /* Тайл [0] виден (плоск. 0) */
|
||||
#define TSCONF_T1Z_EN 0x08 /* Тайл [0] виден (плоск. 1) */
|
||||
#define TSCONF_T0_EN 0x20 /* Tile-layer 0 enable */
|
||||
#define TSCONF_T1_EN 0x40 /* Tile-layer 1 enable */
|
||||
#define TSCONF_S_EN 0x80 /* Sprite layers enable */
|
||||
|
||||
/* ── PalSel (#07AF): выбор палитр ──────────────────────────────────── */
|
||||
/* GPAL[7:4] | T0PAL[7:6] | T1PAL[7:6]. Сброс = 0x0F */
|
||||
#define TSCONF_PORT_PALSEL TSCONF_PORT(0x07)
|
||||
|
||||
/* ── Border (#0FAF): цвет бордюра ──────────────────────────────────── */
|
||||
#define TSCONF_PORT_BORDER TSCONF_PORT(0x0F)
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* УПРАВЛЕНИЕ ПАМЯТЬЮ
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
* RA[21:14] — номер 16KB-страницы DRAM
|
||||
* Page0 = W (write-only), Page1 = W, Page2 = RW, Page3 = RW
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_PAGE0 TSCONF_PORT(0x10) /* окно #0000, W */
|
||||
#define TSCONF_PORT_PAGE1 TSCONF_PORT(0x11) /* окно #4000, W */
|
||||
#define TSCONF_PORT_PAGE2 TSCONF_PORT(0x12) /* окно #8000, RW */
|
||||
#define TSCONF_PORT_PAGE3 TSCONF_PORT(0x13) /* окно #C000, RW */
|
||||
|
||||
/* ── FMAddr (#14AF): адрес Font Maps ───────────────────────────────── */
|
||||
#define TSCONF_PORT_FMADDR TSCONF_PORT(0x14)
|
||||
#define TSCONF_FMADDR_EN 0x10 /* бит 4 = FM_EN. Биты 3:0 = A[15:12] */
|
||||
|
||||
/* ── TMPage (#15AF): страница TileMap ──────────────────────────────── */
|
||||
#define TSCONF_PORT_TMPAGE TSCONF_PORT(0x15) /* RA[21:14] */
|
||||
|
||||
/* ── T0GPage (#16AF): страница графики тайлов плоскости 0 ──────────── */
|
||||
#define TSCONF_PORT_T0GPAGE TSCONF_PORT(0x16) /* RA[21:17] */
|
||||
|
||||
/* ── T1GPage (#17AF): страница графики тайлов плоскости 1 ──────────── */
|
||||
#define TSCONF_PORT_T1GPAGE TSCONF_PORT(0x17) /* RA[21:17] */
|
||||
|
||||
/* ── SGPage (#18AF): страница графики спрайтов ─────────────────────── */
|
||||
#define TSCONF_PORT_SGPAGE TSCONF_PORT(0x18) /* RA[21:17] */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* DMA — адреса источника и назначения
|
||||
* Адрес = 22-бит: [X(8)][H(6)][L(7)][0]
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_DMASADDRL TSCONF_PORT(0x19) /* Source RA[7:1] */
|
||||
#define TSCONF_PORT_DMASADDRH TSCONF_PORT(0x1A) /* Source RA[13:8] */
|
||||
#define TSCONF_PORT_DMASADDRX TSCONF_PORT(0x1B) /* Source RA[21:14] */
|
||||
#define TSCONF_PORT_DMADADDRL TSCONF_PORT(0x1C) /* Dest RA[7:1] */
|
||||
#define TSCONF_PORT_DMADADDRH TSCONF_PORT(0x1D) /* Dest RA[13:8] */
|
||||
#define TSCONF_PORT_DMADADDRX TSCONF_PORT(0x1E) /* Dest RA[21:14] */
|
||||
#define TSCONF_PORT_DMAWPDEV TSCONF_PORT(0x25) /* DMA Wait Port Dev */
|
||||
#define TSCONF_PORT_DMALEN TSCONF_PORT(0x26) /* Burst Length [7:0] */
|
||||
#define TSCONF_PORT_DMACTRL TSCONF_PORT(0x27) /* Ctrl W / Status R */
|
||||
#define TSCONF_PORT_DMANUM TSCONF_PORT(0x28) /* Bursts Num [7:0] */
|
||||
#define TSCONF_PORT_DMANUMH TSCONF_PORT(0x2C) /* Bursts Num [9:8] */
|
||||
#define TSCONF_PORT_DMAWPADDR TSCONF_PORT(0x2D) /* Wait Port Addr */
|
||||
|
||||
/* DMACtrl W — DDEV[3:0]: режим DMA */
|
||||
#define TSCONF_DMA_RAM_RAM 0x09 /* RAM→RAM copy */
|
||||
#define TSCONF_DMA_BLT1_RAM 0x89 /* RAM→RAM, пропуск нулей (OPT=1) */
|
||||
#define TSCONF_DMA_FILL 0x21 /* Fill: src → dest*N */
|
||||
#define TSCONF_DMA_RAM_CRAM 0xA1 /* RAM→CRAM (палитра) */
|
||||
#define TSCONF_DMA_SPI_RAM 0x11 /* SPI→RAM */
|
||||
#define TSCONF_DMA_RAM_SPI 0x91 /* RAM→SPI */
|
||||
#define TSCONF_DMA_IDE_RAM 0x19 /* IDE→RAM */
|
||||
#define TSCONF_DMA_RAM_IDE 0x99 /* RAM→IDE */
|
||||
#define TSCONF_DMA_FDD_RAM 0x29 /* FDD→RAM */
|
||||
#define TSCONF_DMA_RAM_WTP 0xB9 /* RAM→Wait Port */
|
||||
#define TSCONF_DMA_WTP_RAM 0x39 /* Wait Port→RAM */
|
||||
#define TSCONF_DMA_BLT2_RAM 0x31 /* Блиттер с аддером */
|
||||
|
||||
/* DMACtrl — флаги */
|
||||
#define TSCONF_DMA_ASZ_256 0x00 /* Выравнивание 256 байт */
|
||||
#define TSCONF_DMA_ASZ_512 0x04 /* Выравнивание 512 байт */
|
||||
#define TSCONF_DMA_D_ALGN 0x08 /* Выравнивание назначения */
|
||||
#define TSCONF_DMA_S_ALGN 0x10 /* Выравнивание источника */
|
||||
/* R: DMAStatus */
|
||||
#define TSCONF_DMA_ACT 0x80 /* DMA активна */
|
||||
|
||||
/* DMAWPDev */
|
||||
#define TSCONF_DMAWP_GLUCLOCK 0x00
|
||||
#define TSCONF_DMAWP_COMPORT 0x01
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* СИСТЕМНАЯ КОНФИГУРАЦИЯ
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── SysConfig (#20AF): частота CPU и кэш ──────────────────────────── */
|
||||
#define TSCONF_PORT_SYSCONFIG TSCONF_PORT(0x20)
|
||||
#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 /* Включить кэш (все 4 окна) */
|
||||
|
||||
/* ── MemConfig (#21AF) ────────────────────────────────────────────────
|
||||
* ROM128: бит 0 = то же что бит 4 в #7FFD (BootROM/Basic)
|
||||
* W0_WE: разрешить запись в окно #0000
|
||||
* !W0_MAP: 0=маппинг через Page0, 1=стандартная страница 0
|
||||
* W0_RAM: 0=ROM, 1=RAM в #0000
|
||||
* LCK128: режим блокировки 128K-совместимости
|
||||
* ──────────────────────────────────────────────────────────────────── */
|
||||
#define TSCONF_PORT_MEMCONFIG TSCONF_PORT(0x21)
|
||||
#define TSCONF_MEM_ROM128 0x01
|
||||
#define TSCONF_MEM_W0_WE 0x02
|
||||
#define TSCONF_MEM_W0_MAP_DIS 0x04
|
||||
#define TSCONF_MEM_W0_RAM 0x08
|
||||
#define TSCONF_MEM_LCK128_512K 0x00 /* 512K, биты 7:6 #7FFD=Page3[4:3] */
|
||||
#define TSCONF_MEM_LCK128_128K 0x40 /* 128K, биты 7:6 #7FFD=00 */
|
||||
#define TSCONF_MEM_LCK128_AUTO 0x80 /* Auto (!a[13]) */
|
||||
#define TSCONF_MEM_LCK128_1M 0xC0 /* 1024K */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ПРЕРЫВАНИЯ — позиция FRAME-INT
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── HSINT (#22AF): горизонтальная позиция ─────────────────────────── */
|
||||
/* HCNT[8:1] в диапазоне 0–223; >223 = выключить INT в этой строке */
|
||||
#define TSCONF_PORT_HSINT TSCONF_PORT(0x22)
|
||||
#define TSCONF_HSINT_OFF 0xFF
|
||||
|
||||
/* ── VSINTL (#23AF): вертикальная позиция [7:0] ────────────────────── */
|
||||
/* VCNT[7:0]; строки 0–319; >319 = FRAME-INT выключен */
|
||||
#define TSCONF_PORT_VSINTL TSCONF_PORT(0x23)
|
||||
|
||||
/* ── VSINTH (#24AF): вертикальная позиция [8] + автоинкремент ──────── */
|
||||
/* Бит 0 = VCNT[8]; биты [7:4] = VINT_INC[3:0] */
|
||||
/* VINT_INC: после каждого FRAME-INT → VCNT += VINT_INC (0 = нет) */
|
||||
#define TSCONF_PORT_VSINTH TSCONF_PORT(0x24)
|
||||
#define TSCONF_VSINTH_VCNT8 0x01
|
||||
#define TSCONF_VSINTH_INC_MASK 0xF0
|
||||
#define TSCONF_VSINTH_INC(n) (((n) & 0x0F) << 4)
|
||||
|
||||
/* ── INTMask (#2AAF): маска прерываний ─────────────────────────────── */
|
||||
/* Сброс: 0x01 (только FRAME включён) */
|
||||
#define TSCONF_PORT_INTMASK TSCONF_PORT(0x2A)
|
||||
#define TSCONF_INT_FRAME 0x01
|
||||
#define TSCONF_INT_LINE 0x02
|
||||
#define TSCONF_INT_DMA 0x04
|
||||
#define TSCONF_INT_WTP 0x08
|
||||
|
||||
/* Векторы IM2 (значение на шине данных при прерывании) */
|
||||
#define TSCONF_IVEC_FRAME 0xFF
|
||||
#define TSCONF_IVEC_LINE 0xFD
|
||||
#define TSCONF_IVEC_DMA 0xFB
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* КЭШ — CacheConfig (#2BAF)
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_CACHECONFIG TSCONF_PORT(0x2B)
|
||||
#define TSCONF_CACHE_EN_0000 0x01
|
||||
#define TSCONF_CACHE_EN_4000 0x02
|
||||
#define TSCONF_CACHE_EN_8000 0x04
|
||||
#define TSCONF_CACHE_EN_C000 0x08
|
||||
#define TSCONF_CACHE_ALL 0x0F
|
||||
#define TSCONF_CACHE_NONE 0x00
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* FDD VIRTUAL DRIVE — FDDVirt (#29AF)
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_FDDVIRT TSCONF_PORT(0x29)
|
||||
#define TSCONF_FDD_A 0x01
|
||||
#define TSCONF_FDD_B 0x02
|
||||
#define TSCONF_FDD_C 0x04
|
||||
#define TSCONF_FDD_D 0x08
|
||||
#define TSCONF_FDD_VG_OPEN 0x80
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ТАЙЛОВЫЕ ПЛОСКОСТИ — смещения
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_T0XOFFSL TSCONF_PORT(0x40)
|
||||
#define TSCONF_PORT_T0XOFFSH TSCONF_PORT(0x41)
|
||||
#define TSCONF_PORT_T0YOFFSL TSCONF_PORT(0x42)
|
||||
#define TSCONF_PORT_T0YOFFSH TSCONF_PORT(0x43)
|
||||
#define TSCONF_PORT_T1XOFFSL TSCONF_PORT(0x44)
|
||||
#define TSCONF_PORT_T1XOFFSH TSCONF_PORT(0x45)
|
||||
#define TSCONF_PORT_T1YOFFSL TSCONF_PORT(0x46)
|
||||
#define TSCONF_PORT_T1YOFFSH TSCONF_PORT(0x47)
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* HUS — Hurricane Sound Engine (#80AF–#87AF)
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_HUSCTRL TSCONF_PORT(0x80) /* W: управление */
|
||||
#define TSCONF_PORT_HUSSAMPRATE TSCONF_PORT(0x81) /* W: SR[7:0] (1750k/(SR+1)) */
|
||||
#define TSCONF_PORT_HUSTICKRATEL TSCONF_PORT(0x82) /* W: TR[7:0] */
|
||||
#define TSCONF_PORT_HUSTICKRATEH TSCONF_PORT(0x83) /* W: TR[9:8] */
|
||||
#define TSCONF_PORT_HUSRLD0 TSCONF_PORT(0x84) /* Reload ch 0–7 */
|
||||
#define TSCONF_PORT_HUSRLD1 TSCONF_PORT(0x85) /* Reload ch 8–15 */
|
||||
#define TSCONF_PORT_HUSRLD2 TSCONF_PORT(0x86)
|
||||
#define TSCONF_PORT_HUSRLD3 TSCONF_PORT(0x87)
|
||||
/* R: HUSStatus (#80AF) */
|
||||
#define TSCONF_HUS_ACT 0x01
|
||||
#define TSCONF_HUS_TICK 0x02 /* сбрасывается при чтении */
|
||||
#define TSCONF_HUS_HALF 0x40 /* буфер наполовину пуст */
|
||||
#define TSCONF_HUS_EMPTY 0x80
|
||||
/* W: HUSControl */
|
||||
#define TSCONF_HUS_PAUSE 0x02
|
||||
#define TSCONF_HUS_RESET 0x80
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* OPL3 (YMF262) — порты на шине TSConfig
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_OPL3_ADDR0 0x00C4
|
||||
#define TSCONF_PORT_OPL3_DATA0 0x00C5
|
||||
#define TSCONF_PORT_OPL3_ADDR1 0x00C6
|
||||
#define TSCONF_PORT_OPL3_DATA1 0x00C7
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* AY-3-8910 / YM2149 — стандартные порты ZX Spectrum 128
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
* #FFFD — выбор регистра (W) / чтение данных (R)
|
||||
* #BFFD — запись данных (W)
|
||||
*
|
||||
* TurboSound (два AY): запись в #FFFD значения #FF/#FE выбирает чип:
|
||||
* #FF → чип 0 (первый AY); #FE → чип 1 (второй AY).
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_AY_ADDR 0xFFFD /* register select / read */
|
||||
#define TSCONF_PORT_AY_DATA 0xBFFD /* data write */
|
||||
#define TSCONF_AY_TS_CHIP0 0xFF /* TurboSound: select chip 0 */
|
||||
#define TSCONF_AY_TS_CHIP1 0xFE /* TurboSound: select chip 1 */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* SAA1099 — порты SAM Coupé / ZX-совместимые
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
* #01FF — выбор регистра (address write)
|
||||
* #00FF — запись данных (data write)
|
||||
* Второй SAA1099 (если есть):
|
||||
* #01FE — выбор регистра
|
||||
* #00FE — запись данных
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define TSCONF_PORT_SAA_ADDR 0x01FF /* SAA1099 #1 address */
|
||||
#define TSCONF_PORT_SAA_DATA 0x00FF /* SAA1099 #1 data */
|
||||
#define TSCONF_PORT_SAA2_ADDR 0x01FE /* SAA1099 #2 address */
|
||||
#define TSCONF_PORT_SAA2_DATA 0x00FE /* SAA1099 #2 data */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* WC СИСТЕМНЫЕ ПЕРЕМЕННЫЕ — адреса в ROM-области ядра WC
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define WC_ADDR_PAGES 0x6000 /* [3 байта] страницы #0000,#4000,#8000 */
|
||||
#define WC_ADDR_ABT 0x6004 /* флаг ESC */
|
||||
#define WC_ADDR_ENT 0x6005 /* флаг ENTER */
|
||||
#define WC_ADDR_TMN 0x6009 /* таймер (инкр. по INT), uint16 */
|
||||
#define WC_ADDR_CNFV 0x600D /* версия конфигурации */
|
||||
#define WC_ADDR_HEI 0x600E /* высота экрана (25/30/36 строк) */
|
||||
|
||||
#define WC_CNFV_PWM 0x00
|
||||
#define WC_CNFV_3BIT 0x01
|
||||
#define WC_CNFV_4BIT 0x02
|
||||
#define WC_CNFV_5BIT 0x03
|
||||
#define WC_CNFV_VDAC2 0x07
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* SFR — прямой доступ к портам из SDCC-C
|
||||
* ══════════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* __sfr __at 0xNN — 8-bit port: out (N), a / in a, (N)
|
||||
* __sfr __banked __at 0xNNNN — 16-bit port: ld bc,#NNNN; out (c),a
|
||||
*
|
||||
* Не создают переменных/символов — чисто compile-time mapping.
|
||||
* Присваивание sfr_xxx = val; генерирует inline OUT.
|
||||
* Чтение val = sfr_xxx; генерирует inline IN.
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* --- Стандартный ZX Spectrum: 8-bit порт ----------------------------- */
|
||||
__sfr __at 0xFE sfr_zx_fe; /* border / keyboard */
|
||||
|
||||
/* --- TSConfig регистры (addr = reg<<8 | 0xAF) ----------------------- */
|
||||
__sfr __banked __at 0x00AF sfr_vconfig; /* VConfig */
|
||||
__sfr __banked __at 0x01AF sfr_vpage; /* VPage */
|
||||
__sfr __banked __at 0x02AF sfr_gxoffsl; /* GXOffsL */
|
||||
__sfr __banked __at 0x03AF sfr_gxoffsh; /* GXOffsH */
|
||||
__sfr __banked __at 0x04AF sfr_gyoffsl; /* GYOffsL */
|
||||
__sfr __banked __at 0x05AF sfr_gyoffsh; /* GYOffsH */
|
||||
__sfr __banked __at 0x06AF sfr_tsconfig; /* TSConfig */
|
||||
__sfr __banked __at 0x07AF sfr_palsel; /* PalSel */
|
||||
__sfr __banked __at 0x0FAF sfr_border; /* Border Color */
|
||||
__sfr __banked __at 0x10AF sfr_page0; /* Page0 (#0000) */
|
||||
__sfr __banked __at 0x11AF sfr_page1; /* Page1 (#4000) */
|
||||
__sfr __banked __at 0x12AF sfr_page2; /* Page2 (#8000) */
|
||||
__sfr __banked __at 0x13AF sfr_page3; /* Page3 (#C000) */
|
||||
__sfr __banked __at 0x14AF sfr_fmaddr; /* FMAddr */
|
||||
__sfr __banked __at 0x15AF sfr_tmpage; /* TMPage */
|
||||
__sfr __banked __at 0x16AF sfr_t0gpage; /* T0GPage */
|
||||
__sfr __banked __at 0x17AF sfr_t1gpage; /* T1GPage */
|
||||
__sfr __banked __at 0x18AF sfr_sgpage; /* SGPage */
|
||||
__sfr __banked __at 0x20AF sfr_sysconfig; /* SysConfig (CPU) */
|
||||
__sfr __banked __at 0x21AF sfr_memconfig; /* MemConfig */
|
||||
__sfr __banked __at 0x22AF sfr_hsint; /* HSINT */
|
||||
__sfr __banked __at 0x23AF sfr_vsintl; /* VSINTL */
|
||||
__sfr __banked __at 0x24AF sfr_vsinth; /* VSINTH */
|
||||
__sfr __banked __at 0x2AAF sfr_intmask; /* INTMask */
|
||||
__sfr __banked __at 0x2BAF sfr_cacheconfig; /* CacheConfig */
|
||||
__sfr __banked __at 0x29AF sfr_fddvirt; /* FDDVirt */
|
||||
|
||||
/* --- DMA --- */
|
||||
__sfr __banked __at 0x1AAF sfr_dmasaddrh; /* DMA Src Addr H */
|
||||
__sfr __banked __at 0x1BAF sfr_dmasaddrx; /* DMA Src Addr X */
|
||||
__sfr __banked __at 0x19AF sfr_dmasaddrl; /* DMA Src Addr L */
|
||||
__sfr __banked __at 0x1CAF sfr_dmadaddrl; /* DMA Dst Addr L */
|
||||
__sfr __banked __at 0x1DAF sfr_dmadaddrh; /* DMA Dst Addr H */
|
||||
__sfr __banked __at 0x1EAF sfr_dmadaddrx; /* DMA Dst Addr X */
|
||||
__sfr __banked __at 0x26AF sfr_dmalen; /* DMA Burst Length */
|
||||
__sfr __banked __at 0x27AF sfr_dmactrl; /* DMA Ctrl / Status */
|
||||
__sfr __banked __at 0x28AF sfr_dmanum; /* DMA Bursts Num L */
|
||||
__sfr __banked __at 0x2CAF sfr_dmanumh; /* DMA Bursts Num H */
|
||||
|
||||
/* --- OPL3 (YMF262) --- */
|
||||
__sfr __banked __at 0x00C4 sfr_opl3_addr0; /* Bank0 address */
|
||||
__sfr __banked __at 0x00C5 sfr_opl3_data0; /* Bank0 data */
|
||||
__sfr __banked __at 0x00C6 sfr_opl3_addr1; /* Bank1 address */
|
||||
__sfr __banked __at 0x00C7 sfr_opl3_data1; /* Bank1 data */
|
||||
|
||||
/* --- AY-3-8910 / YM2149 --- */
|
||||
__sfr __banked __at 0xFFFD sfr_ay_addr; /* reg select / read */
|
||||
__sfr __banked __at 0xBFFD sfr_ay_data; /* data write */
|
||||
|
||||
/* --- SAA1099 --- */
|
||||
__sfr __banked __at 0x01FF sfr_saa_addr; /* SAA1099 #1 address */
|
||||
__sfr __banked __at 0x00FF sfr_saa_data; /* SAA1099 #1 data */
|
||||
__sfr __banked __at 0x01FE sfr_saa2_addr; /* SAA1099 #2 address */
|
||||
__sfr __banked __at 0x00FE sfr_saa2_data; /* SAA1099 #2 data */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ФУНКЦИИ — обёртки
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/** Запись значения в произвольный TSConfig-порт (asm, для динамических адресов)
|
||||
* @param port TSCONF_PORT(reg)
|
||||
* @param val значение */
|
||||
extern void tsconf_out(uint16_t port, uint8_t val);
|
||||
|
||||
/* ── Удобные макросы (через __sfr — inline, без call) ────────────────── */
|
||||
|
||||
/** Установить скорость CPU (TSCONF_CPU_3_5MHZ / 7MHZ / 14MHZ) */
|
||||
#define tsconf_set_cpu(speed) \
|
||||
(sfr_sysconfig = (uint8_t)((speed) & TSCONF_CPU_MASK))
|
||||
|
||||
/** Установить скорость CPU с управлением кэшем */
|
||||
#define tsconf_set_cpu_cache(speed, cache_en) \
|
||||
(sfr_sysconfig = (uint8_t)(((speed) & TSCONF_CPU_MASK) | ((cache_en) ? TSCONF_CACHE_EN : 0)))
|
||||
|
||||
/** Установить маску прерываний (OR нужных TSCONF_INT_*) */
|
||||
#define tsconf_set_intmask(mask) \
|
||||
(sfr_intmask = (uint8_t)(mask))
|
||||
|
||||
/** Настроить позицию FRAME-INT: line=0–319, hpos=0–223 */
|
||||
#define tsconf_set_frame_int(line, hpos) do { \
|
||||
sfr_vsinth = (uint8_t)(((line) >> 8) & 0x01); \
|
||||
sfr_vsintl = (uint8_t)((line) & 0xFF); \
|
||||
sfr_hsint = (uint8_t)(hpos); \
|
||||
} while(0)
|
||||
|
||||
/** Выключить FRAME-INT полностью */
|
||||
#define tsconf_disable_frame_int() \
|
||||
(sfr_hsint = TSCONF_HSINT_OFF)
|
||||
|
||||
/** Настроить кэш по окнам: маска из TSCONF_CACHE_EN_* */
|
||||
#define tsconf_set_cache(mask) \
|
||||
(sfr_cacheconfig = (uint8_t)(mask))
|
||||
|
||||
/** Установить цвет бордюра (TSConfig-регистр) */
|
||||
#define tsconf_set_border(color) \
|
||||
(sfr_border = (uint8_t)(color))
|
||||
|
||||
/** Подключить видеостраницу (номер 0–63) на адрес #C000 */
|
||||
#define tsconf_set_vpage(page) \
|
||||
(sfr_vpage = (uint8_t)(page))
|
||||
|
||||
/** Установить видеорежим (OR из TSCONF_VM_*, TSCONF_RRES_*, TSCONF_VC_*) */
|
||||
#define tsconf_set_vconfig(cfg) \
|
||||
(sfr_vconfig = (uint8_t)(cfg))
|
||||
|
||||
/** Подключить RAM-страницу page на окно #C000 (Page3) */
|
||||
#define tsconf_set_page3(page) \
|
||||
(sfr_page3 = (uint8_t)(page))
|
||||
|
||||
/** Подключить RAM-страницу page на окно #8000 (Page2) */
|
||||
#define tsconf_set_page2(page) \
|
||||
(sfr_page2 = (uint8_t)(page))
|
||||
|
||||
#endif /* TSCONF_H */
|
||||
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* @file txtlib.h
|
||||
* @brief 䇃䇐䈜䈛 для вывода текста в окна WC — динамические буферы и печать строк
|
||||
* @version 1.0
|
||||
*
|
||||
* Зависимости: wc_api.h (wc_prsrw, wc_window_t).
|
||||
* Реализация: lib/txtlib.c.
|
||||
*
|
||||
* Формат буфера:
|
||||
* buf[0] = capacity — максимальная емкость в символах (задаётся ОДИН РАЗ при buf_init)
|
||||
* buf[1] = len — текущая длина (количество символов)
|
||||
* buf[2..] = символы — не завершается \0!
|
||||
*
|
||||
* Объявление буфера:
|
||||
* char work_buf[66]; // capacity = 64 символа
|
||||
*
|
||||
* Типичное использование:
|
||||
* buf_init(work_buf, 64);
|
||||
* buf_clear(work_buf);
|
||||
* buf_append_str(work_buf, "Time: ");
|
||||
* buf_append_u16_dec(work_buf, seconds);
|
||||
* print_line(&win, 5, work_buf, WC_COLOR(WC_BLACK, WC_BRIGHT_WHITE));
|
||||
*/
|
||||
|
||||
#ifndef TXTLIB_H
|
||||
#define TXTLIB_H
|
||||
|
||||
#include "wc_api.h"
|
||||
|
||||
#define FS_SCREEN_COLS 96U /* макс. ширина экрана (90x36, запас) */
|
||||
|
||||
/**
|
||||
* Вернуть (ширина окна - 4) = полезная ширина содержимого.
|
||||
* Если win->width == 0, используется FS_SCREEN_COLS.
|
||||
*/
|
||||
static inline uint8_t get_content_width(const wc_window_t *win) {
|
||||
uint8_t w = win->width;
|
||||
return (w ? w : FS_SCREEN_COLS) - 4U;
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* ИНИЦИАЛИЗАЦИЯ И БАЗОВЫЕ ОПЕРАЦИИ */
|
||||
/* ================================================================== */
|
||||
|
||||
/** Инициализировать буфер: записать capacity, len=0. Вызывать ОДИН РАЗ! */
|
||||
static inline void buf_init(char *buf, uint8_t capacity) {
|
||||
buf[0] = capacity;
|
||||
buf[1] = 0;
|
||||
}
|
||||
|
||||
/** Очистить буфер: len=0 (емкость не меняется). */
|
||||
static inline void buf_clear(char *buf) {
|
||||
buf[1] = 0;
|
||||
}
|
||||
|
||||
/** Вернуть текущую длину буфера (количество символов). */
|
||||
static inline uint8_t buf_len(const char *buf) {
|
||||
return buf[1];
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* APPEND */
|
||||
/* ================================================================== */
|
||||
|
||||
/**
|
||||
* Добавить строку (\0-terminated) в конец буфера.
|
||||
* Тихо усекается при переполнении (len не превышает capacity).
|
||||
* @return len после добавления
|
||||
*/
|
||||
uint8_t buf_append_str(char *buf, const char *src);
|
||||
|
||||
/**
|
||||
* Добавить один символ c в буфер.
|
||||
* @return len после добавления
|
||||
*/
|
||||
uint8_t buf_append_char(char *buf, char c);
|
||||
|
||||
/**
|
||||
* Добавить двухсимвольное hex-представление байта ("00".."FF").
|
||||
* @param val значение 0..255
|
||||
* @return len после добавления (+2)
|
||||
*/
|
||||
uint8_t buf_append_u8_hex(char *buf, uint8_t val);
|
||||
|
||||
/**
|
||||
* Добавить uint16 в десятичном формате (без ведущих нулей).
|
||||
* @return len после добавления
|
||||
*/
|
||||
uint8_t buf_append_u16_dec(char *buf, uint16_t val);
|
||||
|
||||
/**
|
||||
* Добавить uint32 в десятичном формате.
|
||||
* @return len после добавления
|
||||
*/
|
||||
uint8_t buf_append_u32_dec(char *buf, uint32_t val);
|
||||
|
||||
/**
|
||||
* Добавить "MM:SS" (5 символов) в буфер. Без деления — вычитание.
|
||||
* @param sec секунды (0..5999)
|
||||
*/
|
||||
void buf_append_mmss(char *buf, uint16_t sec);
|
||||
|
||||
|
||||
/* ================================================================== */
|
||||
/* ВЫВОД */
|
||||
/* ================================================================== */
|
||||
|
||||
/**
|
||||
* Вывести одну строку из буфера в окно win заполняя до ширины окна.
|
||||
* Передаётся атрибут цвета attr (EGA: bg[7:4] | fg[3:0]).
|
||||
*
|
||||
* @param win окно WC
|
||||
* @param y строка внутри окна (1-базированное)
|
||||
* @param buf буфер с длиной в buf[1] (формат buf[])
|
||||
* @param attr EGA-атрибут: WC_COLOR(bg, fg)
|
||||
*/
|
||||
void print_line(wc_window_t *win, uint8_t y, const char *buf, uint8_t attr);
|
||||
|
||||
/* ================================================================== */
|
||||
/* ИНИЦИАЛИЗАЦИЯ И ПРИМЕР ИСПОЛЬЗОВАНИЯ */
|
||||
/* ================================================================== */
|
||||
|
||||
/*
|
||||
// В начале плагина (например в init)
|
||||
char meta_buf[122];
|
||||
char work_buf[66]; // 64 символа
|
||||
|
||||
buf_init(meta_buf, 120);
|
||||
buf_init(work_buf, 64); // или больше, если используешь для marquee
|
||||
|
||||
// Заполнение метаданных
|
||||
buf_clear(meta_buf);
|
||||
buf_append_str(meta_buf, "Artist - Super Long Track Name 2025 (Remix)");
|
||||
|
||||
// В цикле отрисовки:
|
||||
uint8_t cw = get_content_width(my_win);
|
||||
|
||||
// Бегущая строка (используем work_buf как временный)
|
||||
build_marquee(work_buf, meta_buf + 2, meta_buf[1], scroll_pos, cw);
|
||||
print_marquee(my_win, 3, work_buf, 0x0F); // белый
|
||||
|
||||
scroll_pos++;
|
||||
|
||||
// Обычная строка (время)
|
||||
buf_clear(work_buf);
|
||||
buf_append_str(work_buf, "Time: ");
|
||||
buf_append_u16_dec(work_buf, minutes);
|
||||
buf_append_char(work_buf, ':');
|
||||
buf_append_u16_dec(work_buf, seconds);
|
||||
print_line(my_win, 5, work_buf, 0x0A);
|
||||
*/
|
||||
|
||||
#endif // TXTLIB_H
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file types.h
|
||||
* @brief Базовые типы данных для Z80/SDCC (sdcccall(1))
|
||||
* @version 2.0
|
||||
*
|
||||
* Самодостаточный заголовок: не зависит от других файлов проекта.
|
||||
* Подходит для любого Z80-проекта на SDCC.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* SDCC sdcccall(1) CALLING CONVENTION (Z80)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Аргумент | Тип | Регистр
|
||||
* ------------|-------------------|----------
|
||||
* 1-й | uint8_t / int8_t | A
|
||||
* 1-й | uint16_t / ptr | DE
|
||||
* 2-й и далее | любой | стек (правый первым)
|
||||
* Возврат | uint8_t | A (расширяется нулём в L)
|
||||
* Возврат | uint16_t / ptr | HL
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* РЕКОМЕНДАЦИИ ДЛЯ Z80
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* - uint32_t в hot-path -> десятки байт кода; избегать.
|
||||
* - volatile — только для переменных, разделяемых с ISR.
|
||||
* - __sfr / __xdata — НЕ использовать (это 8051, не Z80).
|
||||
* - Маленькие inline-функции быстрее, чем call + стек.
|
||||
* - Массивы до 256 элементов — индексировать uint8_t.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "types.h"
|
||||
*
|
||||
* static volatile uint8_t flag; // ISR -> main
|
||||
* static uint16_t counter;
|
||||
*
|
||||
* void timer_tick(void) {
|
||||
* if (++counter >= 2734) { counter = 0; flag = 1; }
|
||||
* }
|
||||
*/
|
||||
|
||||
#ifndef TYPES_H
|
||||
#define TYPES_H
|
||||
|
||||
/* ── Целочисленные типы фиксированной ширины ─────────────────────── */
|
||||
typedef unsigned char uint8_t;
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned int uint16_t; /* SDCC Z80: int = 16 бит */
|
||||
typedef signed int int16_t;
|
||||
typedef unsigned long uint32_t; /* SDCC Z80: long = 32 бит */
|
||||
typedef signed long int32_t;
|
||||
|
||||
/* ── Булев тип ───────────────────────────────────────────────────── */
|
||||
/* uint8_t = один байт; сравнения JR Z / JR NZ без масок. */
|
||||
typedef uint8_t bool_t;
|
||||
#define TRUE ((bool_t)1)
|
||||
#define FALSE ((bool_t)0)
|
||||
|
||||
/* ── NULL ────────────────────────────────────────────────────────── */
|
||||
#ifndef NULL
|
||||
#define NULL ((void *)0)
|
||||
#endif
|
||||
|
||||
/* ── Короткие псевдонимы ─────────────────────────────────────────── */
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef uint32_t u32;
|
||||
typedef int32_t i32;
|
||||
|
||||
#endif /* TYPES_H */
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file variant_cfg.h
|
||||
* @brief Единственный файл конфигурации варианта сборки.
|
||||
*
|
||||
* build_variants.ps1 перегенерирует этот файл целиком для каждого
|
||||
* варианта. Никакие другие исходники НЕ патчатся.
|
||||
*
|
||||
* build.bat читает VARIANT_POS_ENTRIES / VARIANT_POS_STEP /
|
||||
* VARIANT_DATA_LOC из этого файла (парсинг через for /f).
|
||||
*/
|
||||
#ifndef VARIANT_CFG_H
|
||||
#define VARIANT_CFG_H
|
||||
|
||||
/* ── Частота ISR (тиков/сек) ─────────────────────────────────────── */
|
||||
/* ISR_FREQ = 3 500 000 / VARIANT_POS_STEP */
|
||||
#define ISR_FREQ 683
|
||||
|
||||
/* Тиков ISR на TV-кадр (= 71680 / STEP) */
|
||||
/* 14@683, 28@1367, 56@2734 */
|
||||
#define ISR_TICKS_PER_FRAME 14
|
||||
|
||||
/* ── Маппинг VGM 44100→ISR тиков ─────────────────────────────────── */
|
||||
/* SHIFT = log2(44100/ISR_FREQ): 6@683, 5@1367, 4@2734 */
|
||||
#define VGM_SAMPLE_SHIFT 6
|
||||
#define VGM_SAMPLE_MASK 0x3F
|
||||
|
||||
/* ── Лимит команд между принудительными yield ──────────────────────── */
|
||||
#define VGM_FILL_CMD_BUDGET 16
|
||||
|
||||
/* ── Параметры pos_table (для build.bat → gen_pos_table.js) ──────── */
|
||||
#define VARIANT_POS_ENTRIES 14
|
||||
#define VARIANT_POS_STEP 5120
|
||||
|
||||
/* ── Адрес начала сегмента данных (для линковщика) ────────────────── */
|
||||
/* Плотная раскладка: полная версия spectrum.s + загрузчик OPL4.
|
||||
* Проверяется build.bat: _CODE должен закончиться раньше _DATA,
|
||||
* а последняя секция — раньше 0xC000. */
|
||||
#define VARIANT_DATA_LOC 0xB8D0
|
||||
|
||||
/* ── Спектроанализатор: скорости затухания (в TV-кадрах) ────────── */
|
||||
/* Меньше = быстрее затухание. 1 = каждый кадр (~20 мс). */
|
||||
/* Синхронизировать с EQU в spectrum.s! */
|
||||
#define DECAY_FRAMES_FAST 1 /* уровни >= 6 */
|
||||
#define DECAY_FRAMES_MED 2 /* уровни 3-5 */
|
||||
#define DECAY_FRAMES_SLOW 4 /* уровни 1-2 */
|
||||
|
||||
/* ── Отключение обработки коротких пауз 0x70-0x7F ────────────────── */
|
||||
/* Раскомментировать для «no short waits» варианта: */
|
||||
/* #define VGM_NO_SHORT_WAITS */
|
||||
|
||||
#endif /* VARIANT_CFG_H */
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @file version.h
|
||||
* @brief Версия приложения — редактируйте только этот файл
|
||||
*
|
||||
* Примеры:
|
||||
* #define APP_VERSION "v0.5"
|
||||
* #define APP_VERSION "v0.5-beta"
|
||||
* #define APP_VERSION "v1.0-rc1"
|
||||
*/
|
||||
#ifndef VERSION_H
|
||||
#define VERSION_H
|
||||
|
||||
#define APP_VERSION "v0.9.03-beta"
|
||||
|
||||
#endif /* VERSION_H */
|
||||
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* @file vgm.h
|
||||
* @brief VGM-парсер для OPL1/OPL2/OPL3 — заголовок + потоковое чтение
|
||||
* @version 3.0
|
||||
*
|
||||
* Зависимости: types.h, isr.h (cmd_buf_a/b), wc_api.h (wc_mngcvpl).
|
||||
* Реализация: lib/vgm.c.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ОПИСАНИЕ
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Преобразует VGM-поток в ISR-команды (CMD_WRITE_B0/B1, CMD_WAIT,
|
||||
* CMD_END_BUF). VGM данные хранятся в VPL-страницах WC, доступных
|
||||
* через окно #C000.
|
||||
*
|
||||
* Поддерживаемые VGM-команды (OPL1 / OPL2 / OPL3 / OPL4 / AY / SAA):
|
||||
* 0x5A rr dd — YM3812 (OPL2) write → CMD_WRITE_B0
|
||||
* 0x5B rr dd — YM3526 (OPL1) write → CMD_WRITE_B0
|
||||
* 0x5C rr dd — Y8950 (OPL) write → CMD_WRITE_B0
|
||||
* 0x5E rr dd — YMF262 (OPL3) Bank 0 write → CMD_WRITE_B0
|
||||
* 0x5F rr dd — YMF262 (OPL3) Bank 1 write → CMD_WRITE_B1
|
||||
* 0xD0 pp aa dd — YMF278B (OPL4) write, port pp (бит7 = chip select, игн.):
|
||||
* pp&0x7F==0 → Bank 0 (как 0x5E) → CMD_WRITE_B0
|
||||
* pp&0x7F==1 → Bank 1 (как 0x5F) → CMD_WRITE_B1
|
||||
* pp&0x7F==2 → Wave-часть (#7E/#7F) → CMD_WRITE_WAVE
|
||||
* 0x55 rr dd — YM2203 PSG-часть (AY), reg 0-15 → CMD_WRITE_AY
|
||||
* 0xA0 rr dd — AY8910 (чип 1/2 по [7]) → CMD_WRITE_AY / CMD_WRITE_AY2
|
||||
* 0xBD rr dd — SAA1099 (чип 1/2 по [7]) → CMD_WRITE_SAA (bit7 = chip select)
|
||||
* 0x61 nn nn — Wait N samples (16-bit LE)
|
||||
* 0x62 — Wait 735 samples (1/60 сек)
|
||||
* 0x63 — Wait 882 samples (1/50 сек)
|
||||
* 0x66 — End of data
|
||||
* 0x67 ... — Data block (пропускается)
|
||||
* 0x70–0x7F — Wait 1–16 samples
|
||||
* Прочие — пропускаются по таблице VGM spec (1-байт / 2-байт / 3-байт аргументы)
|
||||
*
|
||||
* Масштабирование задержек:
|
||||
* VGM sample rate = 44100 Hz, ISR freq = 2734 Hz -> делим на 16.
|
||||
* Задержки < 16 сэмплов игнорируются (округление вниз).
|
||||
* Неизвестные команды пропускаются по таблице размеров VGM spec.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* VGM ЗАГОЛОВОК — ключевые смещения (little-endian)
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* Offset Size Описание Мин.версия
|
||||
* ------ ---- -------------------------------- ----------
|
||||
* 0x00 4 Сигнатура "Vgm " (0x206D6756) 1.00
|
||||
* 0x08 4 Версия (BCD): 0x0151 = v1.51 1.00
|
||||
* 0x14 4 GD3 offset (отн. 0x14) 1.00
|
||||
* 0x18 4 Total samples 1.00
|
||||
* 0x1C 4 Loop offset (отн. 0x1C) 1.00
|
||||
* 0x20 4 Loop samples 1.00
|
||||
* 0x34 4 Data offset (отн. 0x34) 1.50
|
||||
* 0x50 4 YM3812 (OPL2) clock 1.51
|
||||
* 0x54 4 YM3526 (OPL1) clock 1.51
|
||||
* 0x58 4 Y8950 (OPL) clock 1.51
|
||||
* 0x5C 4 YMF262 (OPL3) clock 1.51
|
||||
*
|
||||
* Для версий < 1.50: data offset фиксирован = 0x40.
|
||||
* Для версий < 1.51: поля чипов за пределами заголовка.
|
||||
*
|
||||
* ~93% OPL VGM файлов — YM3812 (OPL2), команда 0x5A.
|
||||
* ~5% — YMF262 (OPL3), команды 0x5E/0x5F.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
* ПРИМЕР ИСПОЛЬЗОВАНИЯ
|
||||
* ═══════════════════════════════════════════════════════════════════
|
||||
*
|
||||
* #include "vgm.h"
|
||||
* #include "isr.h"
|
||||
* #include "wc_api.h"
|
||||
*
|
||||
* // 1. Загрузить VGM в VPL-страницы
|
||||
* wc_mngcvpl(0);
|
||||
* wc_load512(0xC000, 32); // 16 КБ
|
||||
*
|
||||
* // 2. Разобрать заголовок
|
||||
* if (vgm_parse_header() != VGM_OK) return;
|
||||
*
|
||||
* // 3. Заполнить оба буфера ISR
|
||||
* vgm_fill_buffer(0); // буфер A
|
||||
* vgm_fill_buffer(1); // буфер B
|
||||
*
|
||||
* // 4. В main loop:
|
||||
* if (cur_buf != last_buf) {
|
||||
* vgm_fill_buffer(cur_buf ^ 1); // свободный
|
||||
* last_buf = cur_buf;
|
||||
* }
|
||||
* if (vgm_song_ended && !vgm_rewind_to_loop()) break;
|
||||
*/
|
||||
|
||||
#ifndef VGM_H
|
||||
#define VGM_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/* ── Масштабирование частот PSG под реальное железо ─────────────────── */
|
||||
#define FREQ_MODE_NATIVE 0 /* HW clock ≈ VGM clock (±2%), без пересчёта */
|
||||
#define FREQ_MODE_TABLE 1 /* LUT table match (±5%) */
|
||||
|
||||
/** Текущий режим масштабирования (FREQ_MODE_xxx) */
|
||||
extern uint8_t vgm_freq_mode;
|
||||
|
||||
/** Базовый адрес LUT таблицы в Window 0 (#0000-#3FFF).
|
||||
* Используется в TABLE mode: freq_lut_base[period] → scaled period.
|
||||
* NULL (0) если режим не TABLE.
|
||||
* PSG: индекс 0..4095 (12-bit).
|
||||
* Таблица в plugin page N, подключённой через wc_mng0_pl(N). */
|
||||
extern uint16_t *freq_lut_base;
|
||||
|
||||
/** Clock (кГц) подобранной LUT таблицы (для отображения в UI).
|
||||
* 0 если режим NATIVE. */
|
||||
extern uint16_t vgm_freq_lut_khz;
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* ID чипов = смещение clock-поля в VGM-заголовке.
|
||||
* Имена определены только для поддерживаемых чипов.
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
#define VGM_OFF_SN76489 0x0C /* PSG SN76489 / SN76496 */
|
||||
#define VGM_OFF_YM2413 0x10 /* OPLL */
|
||||
#define VGM_OFF_YM2203 0x44 /* OPN (PC-88) */
|
||||
#define VGM_OFF_YM3812 0x50 /* OPL2 (AdLib / SB) */
|
||||
#define VGM_OFF_YM3526 0x54 /* OPL (ISA AdLib) */
|
||||
#define VGM_OFF_Y8950 0x58 /* MSX-AUDIO / OPL */
|
||||
#define VGM_OFF_YMF262 0x5C /* OPL3 (SB Pro 2) */
|
||||
#define VGM_OFF_YMF278B 0x60 /* OPL4 */
|
||||
#define VGM_OFF_AY8910 0x74 /* AY-3-8910 / YM2149 */
|
||||
#define VGM_OFF_SAA1099 0xC8 /* SAA1099 (SAM Coupé) */
|
||||
|
||||
/* ── Тип OPL-чипа (обратная совместимость) ───────────────────────── */
|
||||
#define VGM_CHIP_NONE 0
|
||||
#define VGM_CHIP_OPL 1 /* YM3526 (OPL1) + Y8950: cmd 0x5B/0x5C */
|
||||
#define VGM_CHIP_OPL2 2 /* YM3812 (OPL2): cmd 0x5A */
|
||||
#define VGM_CHIP_OPL3 3 /* YMF262 (OPL3): cmd 0x5E/5F */
|
||||
#define VGM_CHIP_OPL4 4 /* YMF278B (OPL4): cmd 0xD0 (FM порты 0/1 —
|
||||
* те же регистры, что и OPL3; порт 2 —
|
||||
* wave-часть через #7E/#7F, ZXM-MoonSound) */
|
||||
|
||||
/* Приоритет определения типа: YMF278B > YMF262 > YM3812 > YM3526/Y8950
|
||||
* Используется в build_playback_queue() (main.c) для выбора cmdblock init:
|
||||
* VGM_CHIP_OPL4 → CMDBLK_INIT_OPL4 (NEW=1 + NEW2=1, разблокирует wave-часть)
|
||||
* VGM_CHIP_OPL3 → CMDBLK_INIT_OPL3 (NEW=1, L/R через C0-C8)
|
||||
* иначе → CMDBLK_INIT_OPL2 (NEW=0, L+R авто, OPL2-compat) */
|
||||
|
||||
/* ── Коды результата ─────────────────────────────────────────────── */
|
||||
#define VGM_OK 0
|
||||
#define VGM_ERR_HEADER 1 /* Нет сигнатуры "Vgm " или повреждён */
|
||||
#define VGM_ERR_NOOPL 2 /* Нет OPL-чипа (clock = 0 для всех) */
|
||||
|
||||
/* ── Флаги чипа (vgm_chip_entry_t.flags) ─────────────────────────── */
|
||||
#define VGM_CF_DUAL 0x01 /* Bit 30 clock: два экземпляра чипа */
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
* Структура обнаруженного чипа
|
||||
* ══════════════════════════════════════════════════════════════════════ */
|
||||
typedef struct {
|
||||
uint8_t id; /* VGM_OFF_xxx (header offset of clock) */
|
||||
uint8_t flags; /* VGM_CF_DUAL и пр. */
|
||||
uint16_t clock_khz; /* clock / 1000 (0–65535 кГц) */
|
||||
} vgm_chip_entry_t; /* 4 байта */
|
||||
|
||||
#define VGM_MAX_CHIPS 8 /* макс. чипов в vgm_chip_list[] */
|
||||
|
||||
/* ── Состояние парсера (глобальные переменные) ───────────────────── */
|
||||
|
||||
/** 1 = достигнут конец VGM-данных (команда 0x66 или конец файла) */
|
||||
extern volatile uint8_t vgm_song_ended;
|
||||
|
||||
/** 1 = пауза (буферы заполняются тишиной) */
|
||||
extern uint8_t vgm_paused;
|
||||
|
||||
/** Текущая VPL-страница, отображённая на #C000 */
|
||||
extern uint8_t vgm_cur_page;
|
||||
|
||||
/** Указатель чтения VGM (#C000–#FFFF) */
|
||||
extern uint16_t vgm_read_ptr;
|
||||
|
||||
/** Номер последней VPL-страницы (0-based) */
|
||||
extern uint8_t vgm_end_page;
|
||||
|
||||
/** Адрес конца данных в последней странице */
|
||||
extern uint16_t vgm_end_addr;
|
||||
|
||||
/** Версия VGM (BCD): 0x0151 = v1.51, 0x0171 = v1.71 */
|
||||
extern uint16_t vgm_version;
|
||||
|
||||
/** Тип обнаруженного OPL-чипа (VGM_CHIP_xxx) */
|
||||
extern uint8_t vgm_chip_type;
|
||||
|
||||
/** Массив обнаруженных чипов (для вывода информации о файле) */
|
||||
extern uint8_t vgm_chip_count;
|
||||
extern vgm_chip_entry_t vgm_chip_list[VGM_MAX_CHIPS];
|
||||
|
||||
/** Адрес точки петли (#C000–#FFFF); 0 = нет петли */
|
||||
extern uint16_t vgm_loop_addr;
|
||||
|
||||
/** VPL-страница точки петли */
|
||||
extern uint8_t vgm_loop_page;
|
||||
|
||||
/** Общая длительность воспроизведения (секунды), с учётом loop rewinds */
|
||||
extern uint16_t vgm_total_seconds;
|
||||
|
||||
/** Флаг: использовать ли loop при воспроизведении.
|
||||
* Устанавливается в vgm_parse_header() с учётом loop policy. */
|
||||
extern uint8_t vgm_loop_enabled;
|
||||
|
||||
/* ── Loop/duration policy (читаются из INI, устанавливаются main.c) ── */
|
||||
extern uint8_t cfg_loop_rewinds; /* кол-во повторов петли (default 1) */
|
||||
extern uint8_t cfg_min_duration; /* ниже этой длительности (сек) петля запрещена (default 10) */
|
||||
extern uint16_t cfg_max_duration; /* выше этой длительности (сек) петля запрещена (default 240) */
|
||||
|
||||
/* ── GD3 метаданные (только English, ASCII) ─────────────────────── */
|
||||
#define VGM_GD3_LEN 48 /* макс. длина одного поля (+1 для '\0') */
|
||||
|
||||
extern char vgm_gd3_track[VGM_GD3_LEN]; /* Название трека (EN) */
|
||||
extern char vgm_gd3_game[VGM_GD3_LEN]; /* Название игры (EN) */
|
||||
extern char vgm_gd3_system[VGM_GD3_LEN]; /* Название системы (EN) */
|
||||
extern char vgm_gd3_author[VGM_GD3_LEN]; /* Автор (EN) */
|
||||
|
||||
/* ── Функции ─────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Разобрать заголовок VGM из #C000 (страница 0 должна быть загружена).
|
||||
* Заполняет vgm_read_ptr, vgm_cur_page, vgm_loop_addr/page,
|
||||
* vgm_version, vgm_chip_type, vgm_chip_list[]/vgm_chip_count.
|
||||
* @return VGM_OK (0) или VGM_ERR_HEADER (1)
|
||||
*/
|
||||
uint8_t vgm_parse_header(void);
|
||||
|
||||
/**
|
||||
* Заполнить командный буфер buf_idx (0 = A, 1 = B).
|
||||
*
|
||||
* Поддерживаемые VGM-команды:
|
||||
* 0x5A/0x5B/0x5C/0x5E → CMD_WRITE_B0 (OPL1/OPL2/Y8950/OPL3 bank 0)
|
||||
* 0x5F → CMD_WRITE_B1 (OPL3 bank 1)
|
||||
* 0xD0 (port 0/1/2) → CMD_WRITE_B0 / CMD_WRITE_B1 / CMD_WRITE_WAVE (YMF278B/OPL4)
|
||||
* 0x55 → CMD_WRITE_AY (YM2203 PSG-часть, reg 0-15)
|
||||
* 0xA0 → CMD_WRITE_AY / CMD_WRITE_AY2 (AY8910, dual)
|
||||
* 0xBD → CMD_WRITE_SAA (SAA1099, bit7 = chip select)
|
||||
* 0x61/0x62/0x63/0x70-0x7F → CMD_WAIT
|
||||
* 0x66 → end; 0x67 → data block skip
|
||||
*
|
||||
* Гарантирует CMD_END_BUF в конце буфера.
|
||||
* @param buf_idx 0 = cmd_buf_a, 1 = cmd_buf_b
|
||||
*/
|
||||
void vgm_fill_buffer(uint8_t buf_idx);
|
||||
|
||||
/**
|
||||
* Перемотать VGM на точку петли (если установлена).
|
||||
* Сбрасывает vgm_song_ended = 0 при успехе.
|
||||
* @return 1 = петля есть (перемотано), 0 = нет петли
|
||||
*/
|
||||
uint8_t vgm_rewind_to_loop(void);
|
||||
|
||||
/**
|
||||
* Разобрать GD3-тег (метаданные) из VGM файла.
|
||||
* Вызывать после vgm_parse_header().
|
||||
* Заполняет vgm_gd3_track/game/system/author (ASCII, EN only).
|
||||
* Переключает VPL-страницу — после вызова нужно восстановить
|
||||
* vgm_cur_page через wc_mngcvpl().
|
||||
*/
|
||||
void vgm_parse_gd3(void);
|
||||
|
||||
/**
|
||||
* Получить имя чипа по header offset (VGM_OFF_xxx).
|
||||
* @return указатель на строку (ROM), например "YM3812", "AY8910".
|
||||
* Для неизвестных чипов возвращает NULL.
|
||||
*/
|
||||
const char *vgm_chip_name(uint8_t id);
|
||||
|
||||
/* ─── VGM-format command blocks (cmdblocks page) ────────────────── */
|
||||
|
||||
/** Plugin page содержащая VGM-format command blocks */
|
||||
#define CMDBLK_PAGE 5
|
||||
|
||||
/** Индексы блоков в таблице указателей по адресу 0xC000 */
|
||||
#define CMDBLK_INIT_OPL3 0 /* OPL3 init: NEW=1, 4-op=0, test=0 */
|
||||
#define CMDBLK_INIT_OPL2 1 /* OPL2 compat: NEW=0, 4-op=0, test=0 */
|
||||
#define CMDBLK_SILENCE_OPL 2 /* OPL silence: KeyOff+TL+waveform+BD */
|
||||
#define CMDBLK_SILENCE_AY 3 /* AY chip 1: mixer off, vol=0 */
|
||||
#define CMDBLK_SILENCE_AY2 4 /* AY chip 2 (TS): mixer off, vol=0 */
|
||||
#define CMDBLK_SILENCE_SAA 5 /* SAA1099 chip 1: reset + amp=0 */
|
||||
#define CMDBLK_SILENCE_SAA2 6 /* SAA1099 chip 2: reset + amp=0 */
|
||||
#define CMDBLK_SAA_CLK_ON 7 /* MultiSound: SAA clock enable */
|
||||
#define CMDBLK_SAA_CLK_OFF 8 /* MultiSound: SAA clock disable */
|
||||
#define CMDBLK_SILENCE_YM2203 9 /* YM2203 chip 1: SSG+FM silence */
|
||||
#define CMDBLK_SILENCE_YM2203_2 10 /* YM2203 chip 2 (TS): SSG+FM silence */
|
||||
#define CMDBLK_SAA_INIT 11 /* SAA1099 chip 1: Sound Enable ON */
|
||||
#define CMDBLK_SAA2_INIT 12 /* SAA1099 chip 2: Sound Enable ON+clk */
|
||||
#define CMDBLK_INIT_OPL4 13 /* OPL4 init: NEW=1 + NEW2=1 (wave unlock) */
|
||||
#define CMDBLK_SILENCE_WAVE 14 /* OPL4 wave-часть silence: 24 канала KeyOff+Damp+TL max */
|
||||
|
||||
/* ─── Очередь высокоуровневых команд (HL queue) ───────────────── */
|
||||
|
||||
/** Типы HL-команд */
|
||||
#define HLCMD_CMDBLK 1 /* Выполнить VGM-format cmdblock (param = CMDBLK_xxx) */
|
||||
#define HLCMD_PLAY 2 /* Воспроизведение VGM с текущей позиции до 0x66 */
|
||||
#define HLCMD_LOOP 3 /* Перемотка к loop-точке, затем play (морфируется→PLAY) */
|
||||
#define HLCMD_ISR_DONE 4 /* Записать CMD_ISR_DONE в буфер, ISR замирает */
|
||||
|
||||
#define HL_QUEUE_MAX 16
|
||||
|
||||
typedef struct { uint8_t cmd, param; } hl_entry_t;
|
||||
|
||||
/** Глобальные переменные HL queue — заполняются в main.c (build_playback_queue),
|
||||
* используются внутри vgm_fill_buffer(). */
|
||||
extern hl_entry_t vgm_hl_queue[HL_QUEUE_MAX];
|
||||
extern uint8_t vgm_hl_len; /* всего записей */
|
||||
extern uint8_t vgm_hl_pos; /* текущая запись */
|
||||
extern uint8_t vgm_hl_abort_pos; /* переход при abort пользователя */
|
||||
extern uint8_t vgm_loop_count; /* инкрементируется при каждом LOOP */
|
||||
|
||||
/* ─── VGZ (gzip-сжатый VGM) ─────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Копировать n_pages страниц из мегабуфера (#20+) в TAP (#A1+).
|
||||
* DI на время работы, восстанавливает Win 0 / Win 3 и EI при выходе.
|
||||
*
|
||||
* @param n_pages количество страниц (макс. 31 — размер TAP области)
|
||||
*/
|
||||
void copy_pages_to_tap(uint8_t n_pages);
|
||||
|
||||
/**
|
||||
* Распаковать VGZ из TAP-страниц в мегабуфер.
|
||||
* Вызывается из C — ассемблерная обёртка (inflate_call.s).
|
||||
*
|
||||
* @param src_page первая физ. страница источника (TAP, напр. 0xA1)
|
||||
* @param dst_page первая физ. страница назначения (мегабуфер, напр. 0x20)
|
||||
* @return 0 — успех, 1 — ошибка (неверные данные / не gzip)
|
||||
*/
|
||||
uint8_t inflate_vgz(uint8_t src_page, uint8_t dst_page);
|
||||
|
||||
#endif /* VGM_H */
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user