Scenergy #01
31 мая 1999

Coding - Синхронизация с музыкой в демах.

<b>Coding</b> - Синхронизация с музыкой в демах.
     Синхронизация с музыкой в демах.

 После  просмотра  огромной  кучи дем (как
снаружи,   так  и  просматривая  их  код),
после  разговоров  со  многими  людьми  на
Спектрумовской  demoscene  я  с сожалением
прихожу  к  неутешительным выводам. Дело в
том,     что    наши    славные    кодеры,
проявляющие  чудеса смекалки при написании
различных  эффектов,  практически  все как
один  забыли о такой немаловажной вещи как
синхронизация   изображения   на  экране с
музыкой.   Точнее   все   методы   работы,
касающиеся    синхронизации   по   степени
развития  находятся  просто  в  зачаточном
состоянии.  И  что  еще более грустно - не
заметно    особых   попыток   сдвинуться с
мертвой точки в этом плане.
 Конечно  нельзя  сказать, что все команды
страдают  этим  -  есть и довольно удачные
методы  организации  синхронизации.  Но  в
большинстве случаев используется один из 2
самых популярных методов синхронизации:
 1)   Синхронизация   по  концу  pattern'а
музыки.   Наверняка  этот  метод  появился
первым  т.к.  он наиболее прост и наиболее
убог  по  своим  возможностям.  Суть его в
том,   что  из  плеера  музыки  каким-либо
образом   достаются   данные  о  том,  что
закончился  текущий  pattern  в музыке. По
этому сигналу происходит смена эффекта.
 2)  Счетчик  прерываний.  Метод состоит в
том,  что  на прерывания вешается счетчик,
который      постоянно      инкрементирует
определенную ячейку памяти. Таким образом,
в  этой  ячейке  будет  всегда  находиться
количество прерываний, прошедших с момента
запуска  музыки.  А т.к. плеер музыки тоже
висит  на прерываниях, то можно установить
однозначное соответствие определенной ноты
музыки   и  некоторой  константы,  которая
появится   в   этом   счетчике   в  момент
проигрывания этой ноты.
 Для     того    чтобы    синхронизировать
какой-либо  момент  в  деме с определенной
нотой в музыке - в соответствующее место в
коде   вставляется   кусок  приблизительно
следующего содержания:

        LD DE,<индекс нужной ноты>
LOOP    HALT
        LD HL,(NOTE_COUNTER)
        OR A
        SBC HL,DE
        JR NZ,LOOP

 И   вроде   бы   все   нормально,   можно
синхронизировать  любое  событие  по любой
ноте,  но  все  же есть 2 очень неприятных
момента:
 1) Синхронизация   получается   линейной.
Т.е.  с помощью такого метода можно задать
синхронизацию    только   для   нескольких
последовательно  происходящих событий. При
этом    если   данная   последовательность
изменится    -   придется   править   код.
Организовать  же  при  таком методе работу
нескольких    параллельных   мини-эффектов
(как,  например  в credits к Forever final
release)   причем   в  асинхронном  режиме
представляется     задачей     если     не
невозможной,  то,  по  крайней мере, очень
затруднительной.  Но с этим еще в принципе
можно смириться, если бы не 2-я причина.
 2) При     необходимости      передвинуть
какой-либо     синхронизирующий     момент
придется  править  код!  А  изменение кода
влечет за собой как минимум дополнительную
перекомпиляцию,  выгрузку объектного кода,
его  перепаковку  и  линковку демы заново.
IMHO  достаточно высокая цена за изменение
2-х  байт!  А  если  подходить  к процессу
фикса    демы    под   музыку   достаточно
ответственно  -  необходимость  передвижки
синхронизации будет возникать постоянно. А
тут  еще глобальная нехватка времени из-за
того, что party на носу...
Вообщем, если Вы когда-нибудь писали демы,
Вы поймете, о чем я :)

 Итак, как показал более глубокий анализ -
ни   один   из   этих   методов  не  может
обеспечить      возможность     нормальной
синхронизации.
 Поэтому, наученный горьким  опытом сборки
Binary Love, при написании Forever я решил
подойти  к вопросу  организации  механизма
синхронизации  с музыкой с другой стороны.
 В  результате  была  разработана  система
синхронизации,  совершенно отличающаяся от
всего  что  я  мог  видеть  в демах раньше
(или,  если  это кто-то и сделал до меня -
то   ничего   никому   не   сказал  :)  ).
Впоследствии,    при    написании    CC999
invitation dentro, эта система подверглась
дополнительной  переработке и в результате
на   сегодняшний  момент  я  имею  систему
синхронизации,    которая    лишена   всех
недостатков   описанных   выше  методов, а
кроме  того имеет несколько дополнительных
привлекательных возможностей.
 Близость  CC999,  а  также  выход первого
номера   так   давно   всеми  ожидавшегося
Scenergy  побудили  меня  к написанию этой
статьи  с  описанием  моей  системы. Кроме
того,   в  приложении  Вы  найдете  полные
исходники   intro   от   CC999  invitation
dentro.  Это  прекрасный  пример того, как
используется  данная система синхронизации
т.к. в intro я использовал все возможности
этой  системы.  А  кроме того, intro очень
простое по коду и никому не составит труда
в нем разобраться.
 Итак,  основными  задачами при разработке
данной системы были:
 - Абстрагирование синхронизации  от кода.
   Т.е. я хотел создать систему так, чтобы
   она  позволила  мне  после  сборки всей
   демы   в   кучу  заниматься  только  ее
   синхронизацией с музыкой, совершенно не
   трогая кода.
 - Возможность  асинхронной синхронизации.
   Звучит  несколько  необычно, зато точно
   выражает    суть.    Необходимо    было
   организовать  все   так,  чтобы  я  мог
   синхронизировать   с   музыкой   работу
   нескольких    составляющих      эффекта
   работающих независимо. Например в intro
   к  CC999 invitation dentro  параллельно
   работают:
   - вывод credits.
   - вывод надписи "CC'999".
   - мигания экраном.
   Причем  их  последовательность никак не
   задается в коде!
 После   тщательной  проработки  вопроса я
пришел к выводу о том, что лучшим решением
будет   создание   некоей  псевдопрограммы
синхронизации.  Поэтому общая схема работы
системы синхронизации примерно такова:
 - Для   каждого   эффекта   дополнительно
пишется  своя  маленькая  псевдопрограммка
описывающая,  что  и  когда  делать с этим
эффектом. Конечно термин "псевдопрограмма"
возможно  слишком громко звучит - на самом
деле  это  просто  таблица.  Но  по  своим
функциям она мне гораздо больше напоминает
программку :) Подробнее о ней - ниже.
 - На    прерываниях   в   дополнение    к
стандартному  обработчику прерывания висит
резидент выполняющий следующие функции:
   - управление бордюром
   - проигрывание музыки
   - менеджмент событий синхронизации
 Этот   резидент   как  раз  и  занимается
интерпретацией  "программы" синхронизации.
Причем  здесь  ситуация в корне отличается
от  описанного выше метода со счетчиком. В
нем   эффект   был  главным,  а  счетчик -
подчиненным. Здесь же все наоборот: эффект
играет  роль  подчиненного,  а  резидент -
роль управляющего.

 Перед   тем   как  рассматривать  принцип
работы  резидента - необходимо рассмотреть
подробнее строение таблицы синхронизации.
 Каждый  элемент  таблицы  состоит  из 2-х
полей:
[word] - Значение  счетчика,  при  котором
         произойдет  генерация   сообщения
         синхронизизации.
[byte] - Код генерируемого сообщения.

 Всего может быть 3 типа сообщений:
 -    Синхронизирующие    (M_SYNC).    Они
предназначены    для    того   чтобы   при
необходимости можно было подождать прихода
определенной   ноты.   Для   этого  в  код
вставляется         вызов        процедуры
WAIT_FOR_MARKER.  Можно  также  продолжать
работу,  параллельно  следя  за состоянием
маркера   -   для   этого  есть  процедура
CHECK_FOR_MARKER.   Кстати,   замечу,  что
синхронизирующие   маркеры  накапливаются,
т.е.   если   маркер  пришел,  но  не  был
обработан  - он сохраняется и отдается при
первом    же    вызове    синхронизирующих
процедур. Таким образом исключается потеря
маркеров  на более медленных машинах и как
результат   -   исключены   зацикливания в
процедуре WAIT_FOR_MARKER.
 - "Ударные"   (M_BEAT).  Эти    сообщения
обрабатываются      самим     резидентом и
предназначены   для   того,  чтобы  мигать
экраном под ударники :) При старте эффекта
этот   обработчик   инициализируется,  ему
передаются указатели на обработчики начала
и  конца мигания, скорость мигания и цвета
бордюра  по  умолчанию  и при мигании. При
приходе   маркера   вызывается  обработчик
начала мигания (в приведенном в приложении
intro  это  _START_FLASH),  а затем, через
заданное  количество прерываний вызывается
процедура-обработчик  окончания мигания (в
intro  это  _END_FLASH).  Все  действия по
обеспечению  мигания  берут  на  себя  эти
процедуры,   и   они   свои   для  каждого
эффекта.  Единственное что резидент делает
самостоятельно    -   устанавливает   цвет
бордюра.  Это  унифицированное  управление
бордюром   позволяет  избежать  каких-либо
INT'ов на бордюре.
 -  Custom.  Это  самый  мощный  по  своим
возможностям    тип    маркеров.    Каждое
возможное   событие   получает  уникальный
идентификатор   и   при   приходе  маркера
вызывается  обработчик  custom  markers (в
intro   это   процедура  ACTIONS_HANDLER),
который и выполняет необходимые действия в
зависимости от идентификатора маркера.

 Резидент  висит  на прерываниях, и каждое
прерывание выполняет следующие действия:
1) Инкрементирует счетчик прерываний.
2) Проверяет, не равен ли счетчик значению
в текущей строчке таблицы синхронизации.
3) Если не равен - переход к п.5
4) Проверяется  тип  сообщения. Дальнейшие
действия зависят от типа:
 - M_SYNC.    Инкрементируется     счетчик
   невостребованных       синхронизирующих
   маркеров.   Этот   счетчик  уменьшается
   каждый   раз,   когда  синхронизирующий
   маркер отдается программе,  так что это
   позволяет  накапливать  маркеры  с тем,
   чтобы  потом  отдать  их  по требованию
   программы.
 - M_BEAT.  Вызывается  обработчик  начала
   мигания   экраном.   Также   происходит
   инициализация  внутренних  переменных с
   тем,  чтобы  через  заданный промежуток
   времени   вызвать  процедуру-обработчик
   конца мигания экраном.
   При этом обработчикам  не надо думать о
   состоянии  бордюра - оно  автоматически
   контролируется  резидентом.
 - Custom.   Будет    вызван    обработчик
   синхронизирующих   сообщений   заданных
   пользователем. Указатель на него, также
   как  и  на  все  остальные  необходимые
   процедуры,  передается   резиденту  при
   инициализации эффекта.
   Обработчику   передается  идентификатор
   текущего сообщения  (в регистре  A) и в
   зависимости   от  него   эта  процедура
   должна решать - что же делать.
 Кстати,    custom  messages  также  могут
накапливаться, однако  только  в  пределах
одного  прерывания. Это  дает  возможность
задать несколько синхронизирующих маркеров
срабатывающих в одно прерывание.
 Пункты  2-4  повторяются до тех пор, пока
есть  маркеры, которые  надо  обработать в
этом прерывании.
5) Производится обработка всех накопленных
custom messages. Обработчик вызывается для
каждого custom message.
6) Проверяется    таблица    функций   для
отложенного вызова (об этом - ниже) и, при
необходимости,  вызываются соответствующие
процедуры.
7) Проигрывается музыка.

 Кроме  того,  резидент  поддерживает  еще
один  дополнительный  механизм  напрямую с
синхронизацией    не    связанный,    зато
позволяющий      значительно     облегчить
реализацию некоторых вещей.
 Это  так  называемый механизм отложенного
вызова  функций.  Суть  его  в том, что он
позволяет  вызвать какую-нибудь процедуру,
но   не   сразу,   а   через  определенное
количество прерываний.
 В  качестве примера использования приведу
реальные ситуации все из той же intro.
 1) Т.к.   в   intro   каждое   прерывание
происходит  смена  экранов,  то  в  момент
прихода синхронизирующего сообщения нельзя
предсказать  заранее  -  какой  из экранов
будет  в этот момент видимым. А ведь чтобы
нормально (без мерцания) вывести, например
спрайт  с  credits  необходимо,  чтобы был
включен  именно определенный экран. Что же
делать,   в  случае  если  экран  не  тот?
Использовать  HALT для пропуска прерывания
нельзя   -   произойдет   сбой   в  работе
основного   эффекта   т.к.  эта  процедура
расположена вне основного цикла программы.
Использование механизма отложенного вызова
позволит   решить   этот  вопрос  быстро и
безболезненно:

        LD A,(PAGE)          ;Текущее состояние порта #7FFD
        AND #08              ;Проверяем какой экран включен
        JR Z,...0            ;Включен нужный нам экран
;Включен не тот экран который нам нужен, надо подождать
;следующего прерывания.
        LD HL,VIEW_SPRITE    ;Указатель на эту самую процедуру
        LD A,1               ;Количество пропускаемых INT'ов
        CALL ADD_IM2_HANDLER ;Добавление процедуры для
        RET                  ;отложенного вызова и выход.
...0    ;Рисование спрайта

 2) После  того  как  печатается  спрайт с
очередным   credits   -  он  должен  через
некоторое  время  быть стерт с экрана. Эта
проблема   также   решается  элементарно -
просто  процедура  рисования спрайта сразу
задает отложенный на некоторое время вызов
функции стирания спрайта.

 В реальном коде  резидент оформлен в виде
2-х модулей:
 RESIDENT.A - код резидента.
 RES_EQUS.A - набор   EQUS   для  него.  В
 принципе их можно  объединить, но я люблю
 чтобы все EQUS были в отдельных модулях.

 Кроме    того,   в   самом   intro   есть
специальный  раздел озаглавленный как Demo
system   environment.   Этот  раздел  кода
представляет собой своеобразный "эмулятор"
того окружения, которое будет существовать
в    собранной    деме.    Это   позволяет
впоследствии  перенести  эффект  в готовую
дему  с  минимальными изменениями (которые
просто отключаются условной компиляцией).
 Рассмотрим    так   сказать   "интерфейс"
резидента.  Я буду рассматривать только те
переменные     и     процедуры,    которые
непосредственно   относятся   к   процессу
синхронизации.
               Переменные:

IM2_HANDLER - Указатель  на обработчик IM2
прерывания.

BEAT_START_FLASH - Указатель на процедуру,
которая   будет   вызвана    при   приходе
"ударного"  маркера.  Она  должна привести
экран  к виду,  необходимому  для создания
видимости "моргания" экрана.  Как правило,
это просто заливка  атрибутов  на одном из
экранов белым цветом.

BEAT_END_FLASH - Указатель  на  процедуру,
которая  будет  вызвана  для  того,  чтобы
восстановить экран после "моргания".
FLASH_SPEED   -   Количество    прерываний
отведенных   на   одно  "моргание" экрана.
Проще   говоря,  через   такое  количество
прерываний   будет    вызвана   процедура,
указатель  на которую  задан  в переменной
BEAT_END_FLASH.

BORDER - Цвет  бордюра  по  умолчанию. Как
правило черный.

BEAT_BORDER - Цвет бордюра  при "моргании"
экрана. Как правило белый.
MARKERS_HANDLER - Указатель  на процедуру,
которая будет вызваться при приходе любого
custom сообщения.  Идентификатор сообщения
будет передаваться ей в регистре A.

 Переменные необходимо инициализированы до
того,  как  произойдет  первое обращение к
резиденту!     Собственно,     посмотрев в
исходники    intro,    Вы   увидите,   как
происходит эта инициализация.

                Процедуры:

CHECK_FOR_MARKER - Проверка  существования
синхронизирующего  маркера.  На  выходе из
этой  процедуры флаг CY будет установлен в
том  случае,  если синхронизирующий маркер
был. При этом маркер снимается и считается
что он был успешно отдан программе.
 Пример использования:
MAINLP  ;Основной цикл эффекта
        HALT
        <код эффекта>
        CALL CHECK_FOR_MARKER
        JR NC,MAINLP
        ;Выход из основного цикла
WAIT_FOR_MARKER    -   Ожидание    маркера
синхронизации.  Программа  выйдет  из этой
процедуры  только по приходу маркера. Если
необработанные  маркеры   были - процедура
вернет   управление   вызвавшей  программе
немедленно.

CHECK_FOR_BEAT - Процедура возвращает флаг
CY=1,  если  в   данный  момент   работает
"моргание" экраном.

IM2_RESIDENT - Сам  резидент.  Вызов  этой
процедуры   должен  быть  поставлен  самым
первым в Вашем обработчике прерываний:

IM2     PUSH <всех регистров>
        CALL IM2_RESIDENT
        <все что Вы хотите сделать>
        POP <всех регистров>
        EI
        RET
 Это  необходимо  для  того чтобы избежать
нежелательных  INT'ов на бордюре при смене
цвета бордюра.
ADD_IM2_HANDLER - Процедура, отвечающая за
отложенный  вызов  функций. На входе в нее
задаются:
 HL - Указатель на вызываемую процедуру.
 A  - Количество  пропускаемых прерываний.
      Текущее  прерывание  тоже считается,
      поэтому  A=1 означает, что процедура
      будет вызвана в следующем прерывании

EXIT   -  Процедура   выхода  из  эффекта.
Восстанавливает все необходимые значения и
делает   все   действия   по   обеспечению
корректного   выхода.  Для  ее  нормальной
работы  в  самом  начале  Вашей  программы
необходимо поставить строчку:
             LD (EXIT_SP),SP
 Это  позволит  Вам  выходить  из  эффекта
немедленно,  не дожидаясь завершения цикла
построения очередного кадра.

 Я   не   буду  приводить  здесь  примеров
процедур-обработчиков,    т.к.   все   они
достаточно  элементарны,  чтобы  Вы смогли
разобраться в них сами.
 По   поводу   приведенных   в  приложении
исходников.  Я уже писал в статье про bump
mapping и напишу еще раз: в силу специфики
моего  кода  (а  я  очень  часто использую
синтаксис  специфичный для TASM'а) все мои
исходники нормально компилируются только в
TASM  v4.12  by  RST7/CBS  и нигде больше!
Т.е.  для  того  чтобы  поработать с этими
исходниками   Вам  придется  волей-неволей
использовать  TASM.  Хотя  теоретически их
можно   переделать  под  работу  в  других
ассемблерах,  но  при  этом потеряется вся
гибкость  их настройки, что лично для меня
совершенно неприемлемо.
 Однако, если для Вас использование TASM'а
неприемлемо  (например  по  идеологическим
соображениям   :)   )   -   можете  просто
воспользоваться     приведенными     здесь
описаниями   для   написания   собственной
системы синхронизации.
 Все  копирайты  на идею и исходные тексты
принадлежат мне, так что при использовании
этих    исходников   или   при   написании
собственных,   но   основанных  на  данном
описании  обязательно  упоминание об этом.
На  использование  же  исходников  никаких
ограничений     не     накладывается    за
исключением    того,    что    при   любом
распространении      исходники      должны
оставаться  в неизменном виде! Для личного
использования       допускается      любое
исправление исходников.

PS: Да,  чуть  не забыл!  После компиляции
intro мой макрос будет ругаться:

ALLOCATED FAST MEMORY WILL CORRUPT SOME INCLUDED DATA!!!

 Не стоит пугаться - все нормально :)



Другие статьи номера:

Chars - чарты со 116 анкет собраных коллективом Scenergy.

Charts - лучшие: Organizer, Musician, Coder, Cracking group, Scener, Group, Demo, Mazazine, Party.

Charts - лучшие: Single Coder, Cracker, Graphilian, Organizer, Musician, Group, Site.

Charts - Статистика - цифры и размышления.

Coding - Драйвер RAM DISK'а для ZX-Spectrum'128.

Coding - О пользе макросов или TASM 4.12 - RULEZ!

Coding - реализация Phong Bump Mapping на Speccy.

Coding - Синхронизация с музыкой в демах.

Cracking Scene - крэк конкурс.

Cracking Scene - Нелегальная сцена на Speccy.

Demo Parrt - место проведения CC'999.

Demo Party - мрачный мысли Random'a: "Что мне надо сделать для того, чтобы вы повери в Конструкции Хаоса'999?"

Demo Party - новое демопати: Конструкции Хаоса'999 - прошлое и будущее.

Demo Party - Про Fun Top и всех-всех-всех... (запоздалый репортаж Serzh'a в 5-ти частях)

Demoscene - Chaosite - технические данные по Хаосайту.

Demoscene - история возникновения, состав и софтография Omega Hackers Group.

Demoscene - новая Новгородоская группа "RaZZLeRS".

Demoscene - Открытое письмо для сцены и Kano лично.

Demoscene - состав и контакты литовской группы zERo.

Editorial - Информационный раздел - INDEX...

Groups - статистика об активных группах: Digital Reality

News - Codebuster украли трек у Zhenya? Сайт Fatality переехал. Extreme уходит со спектрума. Ждать ли 4-й номер Subliminal Extacy? Литовская группа Zer0 больше не сущесвует. Phantasy вернулась на сцену! Проблемы с Complex compo. Споры об ассоциациях.

Sceneexplorer - Conments - по материалам анкет: "Что думате о Sprinter и GMX", "где берете программы", "в каких сетях есть адреса".

Sceneexplorer - Результаты анкетирования.

Sceneexplorer - Список участников анкетирования.

Scenergy - Как с нами связаться.

Scenergy - Работа со Scenrgy Setup.

V.I.P. - Интервью c Дэвидом Брабеном, автором великой игры Elite.

V.I.P. - Интервью с галандской демо группой The Lords.

V.I.P. - Интервью с Московской Спектрумовской командой - Cosmic (Progress).

Warez Pack - Описания приложения журнала: Energy Trackmo, Forever: Final Release, Yes.

Warez Pack - Описания приложения журнала: First Association

Warez Pack - Описания приложения журнала: Pussy: Love from Titanic, CrossWord.

Warez Pack - Описания приложения журнала: SpriteLand v1.27

Warez Pack - Описания приложения журнала: Worms


Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Миска - Тривиальное чтиво: "Декларации демократического общества сами по себе как обещания ничeго не стоят. У нас привыкли верить красивым словам, но в поступки верить почему то не принято"
party zone ][ - правила twilight demoparty 2oo2.
scene today - итоги 2008 года: zx.pk.ru, zxaaa.untergrund.net, speccy-live.untergrund.net, cyberpunks unity, triebkraft, milytia, simbols, tiboh/debris, triumph.
Игры - TOWDIE.
Раскрутка - Описание игры "Starglider 2".

В этот день...   21 ноября