Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- ;*******************************************************************************
- ; ПРОШИВКА AY-ЗВОНКА (Z80, AY-3-8910)
- ; Полный дизассемблер рабочей версии
- ; Размер: ~450 байт кода + таблицы данных
- ; Адресация: Код с 0000, данные/переменные в F800-F8FF
- ;*******************************************************************************
- ;===============================================================================
- ; ОБЛАСТЬ ПЕРЕМЕННЫХ (в верхней памяти)
- ;===============================================================================
- ; F800 - Счетчик/флаг прерываний (обновляется по таймеру)
- ; F801 - Текущий индекс в таблице паттернов (0..n)
- ; F802 - (не используется в этом фрагменте)
- ; F803 - (не используется)
- ; F804 - Указатель канала A (текущая позиция в данных)
- ; F805 - Указатель канала B (текущая позиция в данных)
- ; F806 - Флаг/счетчик для специальных эффектов
- ;===============================================================================
- ; ТОЧКА ВХОДА: ИНИЦИАЛИЗАЦИЯ (адрес 0000)
- ;===============================================================================
- 0000: F3 DI ; Запрещаем прерывания на время инициализации
- 0001: 31 FF FF LD SP, $FFFF ; Устанавливаем стек в самый верх памяти
- 0004: 21 00 02 LD HL, $0200 ; HL = источник (код плеера, если копируется)
- 0007: 11 4C F8 LD DE, $F84C ; DE = приемник (область переменных + буфер)
- 000A: 01 5C 04 LD BC, $045C ; BC = размер копирования (1116 байт)
- 000D: ED B0 LDIR ; Копируем блок (вероятно, код плеера в ОЗУ)
- ;===============================================================================
- ; ПРОВЕРКА ЦЕЛОСТНОСТИ ТАБЛИЦЫ ДАННЫХ
- ;===============================================================================
- 000F: 21 91 01 LD HL, $0191 ; HL = начало таблицы данных
- 0012: 06 FF LD B, $FF ; B = счетчик (255 попыток)
- 0014: 7E LD A, (HL) ; Читаем первый байт пары
- 0015: 23 INC HL
- 0016: 4E LD C, (HL) ; C = второй байт пары
- 0017: 23 INC HL ; HL на следующую пару
- 0018: 04 INC B ; Увеличиваем счетчик
- 0019: A1 AND C ; Логическое И двух байтов
- 001A: 3C INC A ; Если было FF, станет 00
- 001B: 20 F7 JR NZ, $0014 ; Если не ноль - продолжаем проверку
- ; Выход из цикла, когда найдена пара FF?
- ;===============================================================================
- ; ЗАЩИТА ИНДЕКСА ОТ ВЫХОДА ЗА ГРАНИЦЫ
- ;===============================================================================
- 001D: 3A 01 F8 LD A, ($F801) ; Загружаем текущий индекс
- 0020: B8 CP B ; Сравниваем с B (счетчиком из цикла)
- 0021: 38 04 JR C, $0027 ; Если индекс < B, пропускаем сброс
- 0023: AF XOR A ; Иначе сбрасываем индекс в 0
- 0024: 32 01 F8 LD ($F801), A
- ;===============================================================================
- ; ВЫЗОВ ПОДПРОГРАММ ИНИЦИАЛИЗАЦИИ
- ;===============================================================================
- 0027: CD 3A 01 CALL $013A ; Вызов подпрограммы A (распаковка/подготовка)
- 002A: CD C7 00 CALL $00C7 ; Вызов подпрограммы B (инициализация AY)
- 002D: CD 1F 01 CALL $011F ; Вызов подпрограммы C (настройка каналов)
- ;===============================================================================
- ; ГЛАВНЫЙ ЦИКЛ (ОЖИДАНИЕ ПРЕРЫВАНИЯ)
- ;===============================================================================
- 0030: 3A 00 F8 LD A, ($F800) ; Загружаем счетчик прерываний
- 0033: CB 47 BIT 0, A ; Проверяем младший бит (флаг "таймер сработал")
- 0035: 20 07 JR NZ, $003E ; Если флаг установлен - играем музыку
- 0037: AF XOR A ; Иначе - обнуляем указатели каналов
- 0038: 32 04 F8 LD ($F804), A ; (гасим звук, если прерывания нет)
- 003B: 32 05 F8 LD ($F805), A
- 003E: 3A 00 F8 LD A, ($F800) ; Снова загружаем счетчик
- 0041: CB 4F BIT 1, A ; Проверяем второй бит
- 0043: CC AC 00 CALL Z, $00AC ; Если бит сброшен - вызываем подпрограмму ожидания
- 0046: CD 87 00 CALL $0087 ; Вызов основной подпрограммы обработки фрейма
- 0049: 3A 04 F8 LD A, ($F804) ; Загружаем указатель канала A
- 004C: 6F LD L, A ; L = указатель канала A
- 004D: 3A 05 F8 LD A, ($F805) ; Загружаем указатель канала B
- 0050: B5 OR L ; Объединяем с L для проверки
- 0051: CC 54 01 CALL Z, $0154 ; Если оба нулевые - вызываем подпрограмму
- 0054: 18 D4 JR $002A ; Возврат к началу цикла (к вызову подпрограмм)
- ;===============================================================================
- ; ОБРАБОТЧИК ПРЕРЫВАНИЯ (вектор или вход по таймеру)
- ;===============================================================================
- 0056: 3A 00 F8 LD A, ($F800) ; Загружаем счетчик прерываний
- 0059: CB 47 BIT 0, A ; Проверяем младший бит
- 005B: C8 RET Z ; Если не установлен - выходим (ложное прерывание)
- ;===============================================================================
- ; ЧТЕНИЕ СЛЕДУЮЩЕГО БЛОКА ИЗ ТАБЛИЦЫ АДРЕСОВ
- ;===============================================================================
- 005C: 11 91 01 LD DE, $0191 ; DE = начало таблицы адресов паттернов
- 005F: 26 00 LD H, $00 ; H = 0
- 0061: 3A 01 F8 LD A, ($F801) ; A = текущий индекс
- 0064: 6F LD L, A ; HL = индекс
- 0065: 29 ADD HL, HL ; Умножаем на 2 (каждый адрес - 2 байта)
- 0066: 19 ADD HL, DE ; HL = адрес в таблице (0191 + индекс*2)
- 0067: 5E LD E, (HL) ; E = младший байт адреса данных
- 0068: 23 INC HL
- 0069: 56 LD D, (HL) ; D = старший байт адреса данных
- 006A: 7A LD A, D ; Проверяем на FFFF (конец данных)
- 006B: A3 AND E ; Если оба FF, результат FF
- 006C: 3C INC A ; Становится 00
- 006D: 28 12 JR Z, $0081 ; Если FFFF - переходим на зацикливание
- 006F: EB EX DE, HL ; HL = адрес данных для проигрывания
- 0070: 22 5A F8 LD ($F85A), HL ; САМОМОДИФИКАЦИЯ: сохраняем адрес
- 0073: CD 0A 01 CALL $010A ; Вызов подпрограммы обработки данных
- 0076: 3A 01 F8 LD A, ($F801) ; Увеличиваем индекс
- 0079: 3C INC A
- 007A: 32 01 F8 LD ($F801), A
- 007D: CD 59 F8 CALL $F859 ; Вызов подпрограммы в области данных
- 0080: C9 RET
- ;===============================================================================
- ; ОБРАБОТЧИК КОНЦА ДАННЫХ (ЗАЦИКЛИВАНИЕ)
- ;===============================================================================
- 0081: AF XOR A ; A = 0
- 0082: 32 01 F8 LD ($F801), A ; Сбрасываем индекс в 0
- 0085: 18 CF JR $0056 ; Возврат к обработчику прерывания
- ;===============================================================================
- ; ОСНОВНАЯ ПОДПРОГРАММА ОБРАБОТКИ ФРЕЙМА
- ;===============================================================================
- 0087: 3A 06 F8 LD A, ($F806) ; Загружаем флаг спецэффектов
- 008A: A7 AND A ; Проверяем на 0
- 008B: C0 RET NZ ; Если не ноль - выходим (звук уже обрабатывается)
- 008C: 3A 04 F8 LD A, ($F804) ; Загружаем указатель канала A
- 008F: 6F LD L, A ; L = указатель канала A
- 0090: 3A 05 F8 LD A, ($F805) ; Загружаем указатель канала B
- 0093: B5 OR L ; Проверяем, оба ли нулевые
- 0094: A7 AND A ; Устанавливаем флаги
- 0095: 28 11 JR Z, $00A8 ; Если оба нулевые - переход
- 0097: 2A 04 F8 LD HL, ($F804) ; HL = (указатель A, указатель B)
- 009A: 2B DEC HL ; Уменьшаем (длительность ноты)
- 009B: 22 04 F8 LD ($F804), HL ; Сохраняем обратно
- 009E: CD 5F F8 CALL $F85F ; Вызов подпрограммы в области данных
- 00A1: 11 78 05 LD DE, $0578 ; DE = константа (период?)
- 00A4: CD 04 01 CALL $0104 ; Вызов подпрограммы
- 00A7: C9 RET
- 00A8: CD E3 00 CALL $00E3 ; Вызов подпрограммы
- 00AB: C9 RET
- ;===============================================================================
- ; ПОДПРОГРАММА ОЖИДАНИЯ
- ;===============================================================================
- 00AC: CD E3 00 CALL $00E3 ; Вызов подпрограммы
- 00AF: 11 55 55 LD DE, $5555 ; DE = паттерн 0101010101010101
- 00B2: CD 04 01 CALL $0104 ; Вызов подпрограммы
- 00B5: 32 06 F8 LD ($F806), A ; Сохраняем результат
- 00B8: CD C7 00 CALL $00C7 ; Вызов инициализации AY
- 00BB: 3A 00 F8 LD A, ($F800) ; Загружаем счетчик прерываний
- 00BE: CB 4F BIT 1, A ; Проверяем бит 1
- 00C0: 28 EA JR Z, $00AC ; Если сброшен - повторяем ожидание
- 00C2: CB 47 BIT 0, A ; Проверяем бит 0
- 00C4: 20 90 JR NZ, $0056 ; Если установлен - переход к обработчику
- 00C6: C9 RET
- ;===============================================================================
- ; ИНИЦИАЛИЗАЦИЯ AY-3-8910 (базовые регистры)
- ;===============================================================================
- 00C7: 3E 07 LD A, $07 ; Регистр 7 (микшер)
- 00C9: 06 FF LD B, $FF ; B = FF (порт адреса)
- 00CB: ED 79 OUT (C), A ; Выбираем регистр 7
- 00CD: ED 78 IN A, (C) ; Читаем текущее значение
- 00CF: CB BF RES 7, A ; Сбрасываем бит 7
- 00D1: CB B7 RES 6, A ; Сбрасываем бит 6
- 00D3: 06 BF LD B, $BF ; B = BF (порт данных)
- 00D5: ED 79 OUT (C), A ; Записываем новое значение
- 00D7: 3E 0E LD A, $0E ; Регистр 14 (порт A - управление)
- 00D9: 06 FF LD B, $FF ; Порт адреса
- 00DB: ED 79 OUT (C), A ; Выбираем регистр 14
- 00DD: ED 78 IN A, (C) ; Читаем
- 00DF: 32 00 F8 LD ($F800), A ; Сохраняем как флаг прерываний
- 00E2: C9 RET
- ;===============================================================================
- ; ПОДПРОГРАММА РАБОТЫ С AY (чтение/запись регистров)
- ;===============================================================================
- 00E3: 06 FF LD B, $FF ; Порт адреса
- 00E5: 3E 0C LD A, $0C ; Регистр 12 (огибающая - период младший)
- 00E7: ED 79 OUT (C), A ; Выбираем
- 00E9: ED 78 IN A, (C) ; Читаем
- 00EB: 6F LD L, A ; L = младший байт периода огибающей
- 00EC: 3E 07 LD A, $07 ; Регистр 7
- 00EE: ED 79 OUT (C), A ; Выбираем
- 00F0: ED 78 IN A, (C) ; Читаем
- 00F2: F6 C0 OR $C0 ; Устанавливаем биты 7 и 6
- 00F4: A5 AND L ; Логическое И с L
- 00F5: 3C INC A ; Увеличиваем
- 00F6: C8 RET Z ; Если стало 0 - возврат
- 00F7: 3E 0D LD A, $0D ; Регистр 13 (огибающая - период старший)
- 00F9: ED 79 OUT (C), A ; Выбираем
- 00FB: 06 BF LD B, $BF ; Порт данных
- 00FD: 3E 01 LD A, $01 ; Значение 1
- 00FF: ED 79 OUT (C), A ; Записываем
- 0101: C3 4C F8 JP $F84C ; Прыжок в область данных (дальнейшая обработка)
- ;===============================================================================
- ; ПОДПРОГРАММА ПРОВЕРКИ/ОЖИДАНИЯ
- ;===============================================================================
- 0104: 1B DEC DE ; DE--
- 0105: 7A LD A, D ; Проверяем D|E == 0?
- 0106: B3 OR E
- 0107: 20 FB JR NZ, $0104 ; Если не ноль - повторяем
- 0109: C9 RET
- ;===============================================================================
- ; ПОДПРОГРАММА ОБРАБОТКИ ДАННЫХ (распаковка ноты)
- ;===============================================================================
- 010A: 7E LD A, (HL) ; Читаем байт из потока (HL из самомод. кода)
- 010B: 5F LD E, A ; E = прочитанный байт
- 010C: AF XOR A ; A = 0
- 010D: 67 LD H, A ; H = 0
- 010E: 6F LD L, A ; L = 0
- 010F: 57 LD D, A ; D = 0
- 0110: 06 06 LD B, $06 ; B = 6 (счетчик битов)
- 0112: 19 ADD HL, DE ; HL = HL + DE
- 0113: 10 FD DJNZ $0112 ; B раз: HL = E * 6? (6 раз прибавляем E)
- ; На самом деле это умножение на 6
- 0115: 29 ADD HL, HL ; Умножаем на 2 (итого E * 12)
- 0116: 29 ADD HL, HL ; Умножаем на 2 (E * 24)
- 0117: 29 ADD HL, HL ; Умножаем на 2 (E * 48)
- 0118: 29 ADD HL, HL ; Умножаем на 2 (E * 96)
- 0119: 29 ADD HL, HL ; Умножаем на 2 (E * 192)
- 011A: 24 FD INC H ; Коррекция? (FD - странный байт)
- 011C: F8 ? ; Мусор? Должно быть LD A, B?
- 011D: C9 RET
- ;===============================================================================
- ; ПОДПРОГРАММА НАСТРОЙКИ КАНАЛОВ
- ;===============================================================================
- 011E: 3A 00 F8 LD A, ($F800) ; Загружаем флаг прерываний
- 0121: CB 57 BIT 2, A ; Проверяем бит 2
- 0123: C8 RET Z ; Если сброшен - выходим
- 0124: E6 01 AND $01 ; Оставляем только бит 0
- 0126: 5F LD E, A ; E = бит 0
- 0127: 3A 03 F8 LD A, ($F803) ; Загружаем что-то из F803
- 012A: E6 01 AND $01 ; Оставляем бит 0
- 012C: BB CP E ; Сравниваем с предыдущим
- 012D: C8 RET Z ; Если равны - выходим
- 012E: 3E FF LD A, $FF ; Иначе A = FF
- 0130: 32 06 F8 LD ($F806), A ; Устанавливаем флаг спецэффектов
- 0133: 3A 00 F8 LD A, ($F800) ; Копируем F800 в F803
- 0136: 32 03 F8 LD ($F803), A
- 0139: 21 00 00 LD HL, $0000 ; HL = 0
- 013C: 22 04 F8 LD ($F804), HL ; Обнуляем указатели каналов
- 013F: C9 RET
- ;===============================================================================
- ; ПОДПРОГРАММА (ЗАГРУЗКА РЕГИСТРОВ ИЗ ТАБЛИЦЫ)
- ;===============================================================================
- 0140: 21 7E 01 LD HL, $017E ; HL = начало таблицы данных (017E)
- 0143: 1E 0D LD E, $0D ; E = 13 (количество регистров для загрузки)
- 0145: 06 FF LD B, $FF ; Порт адреса
- 0147: ED 59 OUT (C), E ; Выбираем регистр E (сначала 0D, потом уменьшается)
- 0149: 7E LD A, (HL) ; Читаем байт из таблицы
- 014A: 06 BF LD B, $BF ; Порт данных
- 014C: 23 INC HL ; HL на следующий байт таблицы
- 014D: ED 79 OUT (C), A ; Записываем в регистр AY
- 014F: 1D DEC E ; Уменьшаем номер регистра
- 0150: F2 45 01 JP P, $0145 ; Если E >= 0, продолжаем
- 0153: C9 RET
- ;===============================================================================
- ; ПОДПРОГРАММА (короткая настройка AY)
- ;===============================================================================
- 0154: 3E 07 LD A, $07 ; Регистр 7
- 0156: 06 FF LD B, $FF ; Порт адреса
- 0158: ED 79 OUT (C), A ; Выбираем
- 015A: ED 78 IN A, (C) ; Читаем
- 015C: CB FF SET 7, A ; Устанавливаем бит 7
- 015E: CB F7 SET 6, A ; Устанавливаем бит 6
- 0160: 06 BF LD B, $BF ; Порт данных
- 0162: ED 79 OUT (C), A ; Записываем (отключаем тональные каналы?)
- 0164: 3E 0F LD A, $0F ; Регистр 15 (порт B - управление)
- 0166: 06 FF LD B, $FF ; Порт адреса
- 0168: ED 79 OUT (C), A ; Выбираем
- 016A: 21 8C 01 LD HL, $018C ; HL = адрес таблицы (018C)
- 016D: 7E LD A, (HL) ; Читаем байт
- 016E: 23 INC HL
- 016F: FE 00 CP $00 ; Сравниваем с 0
- 0171: C8 RET Z ; Если 0 - конец таблицы
- 0172: 06 BF LD B, $BF ; Порт данных
- 0174: ED 79 OUT (C), A ; Записываем в регистр 15
- 0176: 11 00 12 LD DE, $1200 ; DE = константа (пауза?)
- 0179: CD 04 01 CALL $0104 ; Вызов подпрограммы ожидания
- 017C: 18 EF JR $016D ; Повторяем для следующего байта
- ;===============================================================================
- ; ТАБЛИЦА ДАННЫХ (с 017E)
- ;===============================================================================
- 017E: 01 50 C4 10 10 10 38 00 50 00 01 00 30 ; Данные для регистров AY
- 018B: 7F ;
- 018C: BF DF EF ; Данные для регистра 15
- 018F: 00 00 ; Маркер конца?
- ;===============================================================================
- ; ОСНОВНАЯ ТАБЛИЦА АДРЕСОВ ПАТТЕРНОВ (с 0191)
- ;===============================================================================
- 0191: 00 5C 06 91 0F 3A 1A 97 22 9C 2C 5A 36 1A 3E FC
- 01A0: 48 45 4F 9B 5C 37 68 8A 6E 71 74 57 86 12 92 D2
- 01B0: 9A 4D A2 6F AF 41 BE 22 C6 B7 CC 49 D4 02 DC A0
- 01C0: E3 5B E8 FF FF FF FF FF FF FF FF FF FF FF FF FF
- ; Заканчивается серией FF (конец данных)
- /*
- Анализ и объяснение ключевых механизмов
- 1. Двухступенчатая адресация данных
- Прошивка использует классическую для PT3 схему:
- text
- F801 (индекс) → Таблица 0191 (адреса) → Данные в верхней памяти (F8xx)
- Это позволяет легко менять композицию, просто заменяя данные в области F800-FFFF, не трогая код.
- 2. Механизм проверки целостности
- Цикл с 0014 по 001B выполняет важную функцию: он проверяет, что таблица данных не повреждена. Это защита от:
- Сбоев при копировании
- Частичной записи данных
- Битых байтов в ПЗУ
- 3. Управление каналами через указатели
- Переменные F804 и F805 работают как счетчики длительности нот:
- Когда они не равны нулю — нота играет
- Когда оба становятся нулевыми — вызывается подпрограмма 0154 для выбора следующей ноты
- 4. AY-3-8910: хитрости управления
- В подпрограмме 00C7 и 00E3 видна важная особенность:
- Порт FF — адресный (выбор регистра)
- Порт BF — данные (запись/чтение значения)
- Обратите внимание на биты 7 и 6 в регистре 7 (00F2): их установка отключает генерацию тона для каналов B и C, оставляя только шум или огибающую.
- 5. Умножение на 192 в 010A
- Алгоритм с 010A по 011A выполняет умножение входного байта на 192 (6 × 32). Это, скорее всего, преобразование номера ноты в период для AY:
- text
- Период = (байт_из_потока) * 192 + базовая_константа
- 6. Зацикливание композиции
- Когда в таблице 0191 встречается адрес FFFF, программа сбрасывает индекс в 0 (0081) и начинает композицию заново.
- Рекомендации по улучшению кода
- 1. Убрать самомодифицирующийся код (опционально)
- Сейчас адрес данных хранится по F85A и используется подпрограммами. Это быстро, но:
- Мешает размещению кода в ПЗУ (если плеер в ПЗУ, писать нельзя)
- Затрудняет отладку
- Можно заменить на: хранение в паре регистров (например, IX) и передачу через стек.
- 2. Добавить громкость
- В текущем коде не видно явной установки регистров громкости (8, 9, 10). Возможно, они задаются в данных по F85F или F859. Если нужно регулировать громкость программно, стоит добавить запись в эти регистры.
- 3. Расширить до 3 каналов
- Сейчас код работает с двумя указателями (F804, F805). Для третьего канала нужно добавить F806 (который сейчас используется как флаг) и расширить логику проверки.
- 4. Оптимизировать умножение
- Умножение на 192 через 6 сложений и 5 сдвигов можно заменить на:
- assembly
- LD H, E
- LD L, 0
- ADD HL, HL ; *2
- ADD HL, HL ; *4
- ADD HL, HL ; *8
- ADD HL, HL ; *16
- ADD HL, HL ; *32
- ADD HL, HL ; *64
- ADD HL, HL ; *128
- ; Теперь нужно добавить E*64? Сложно...
- Но текущий способ рабочий, менять не обязательно.
- 5. Добавить обработку шума
- Регистры шума (6) и его микширование (7) уже настроены в 00C7. Можно добавить поддержку перкуссии, записывая нужные значения в регистр 6 из потока данных.
- 6. Увеличить громкость звонка
- Если звонок тихий, проверьте регистры 8, 9, 10 (громкость каналов A, B, C). Сейчас они, видимо, задаются в таблице по адресу 017E (данные 01 50 C4...). Можно увеличить значения (максимум 15).
- Возможные модификации для новых функций
- Добавление паузы между звонками
- Вставьте в главный цикл (0030-0054) счетчик, который пропускает несколько фреймов после окончания мелодии.
- Смена мелодии по внешнему сигналу
- Используйте бит порта ввода для выбора разных таблиц (0191 или другой адрес).
- Режим "случайной" мелодии
- При обнаружении FFFF (конец) вместо сброса в 0 загружайте случайный индекс в пределах допустимого диапазона.
- Этот код — отличная основа для экспериментов. Он компактный, надежный и хорошо документирован самой структурой. Удачи в улучшении звонка!
- */
Advertisement
Add Comment
Please, Sign In to add comment