В играх, где на заднем плане присутствует AY-музыка, программист часто сталкивается с проблемой того, что плеер музыки неравномерно нагружает процессор. И в те моменты, когда высокая нагрузка на процессор от графических процедур совпадает с высокой нагрузкой от плеера музыки, можно наблюдать, как игра не укладывается в кадр, происходит срыв плавности движения.
С данной проблемой обычно боролись путем оптимизации плеера музыки, попытками сделать нагрузку от плеера на процессор более равномерной. Уже в плеере PT2 авторы отрабатывают новую ноту на каждый канал не одновременно, а в трех разных фреймах, сохраняя промежуточные результаты. Эти и другие подобные меры хоть и улучшают ситуацию, но все равно не могут обеспечить полностью равномерной нагрузки на процессор. Кроме того, указанные меры часто приводят к повышению общей нагрузки на процессор из-за усложнения плеера, хранения и пересылки промежуточных величин и т.д.
Предлагаю вашему вниманию способ, который, практически не увеличивая среднюю нагрузку на процессор; с минимальными затратами памяти, позволяет решить описанную проблему. Способ универсальный, его можно применить к любому существующему плееру, т.е. разрабатывать новый плеер не требуется.
Идея в том, чтобы организовать очередь из AY-таблиц. Обычно в каждом плеере есть одна таблица, куда плеер складывает значения для последующего вывода в регистры AY. А мы организуем несколько таких таблиц. Например, штук 5. До начала анимации вызываем плеер 5 раз - в результате в очереди будут подготовлены значения для вывода в AY на 5 кадров вперед.
Теперь рассмотрим типичную структуру игрового движка. По прерыванию движок первым делом выводит в AY содержимое AY-таблицы, которое стоит первым в очереди. Это занимает немного времени, и нагрузка на процессор одинакова каждый кадр. Вывод в AY в первую очередь по прерыванию необходим для того, чтобы содержимое регистров AY обновлялось равномерно во времени, а не в зависимости от нагрузки на графический движок.
После обновления регистров AY работает графический движок, который синхронизирован с видео-разверткой. Как правило, нагрузка на процессор от графического движка неравномерна. В идеале она близка к 100% (чтобы возможности процессора полностью использовались для пользы), однако на практике так бывает редко, и нагрузка может колебаться в районе от 60 до 100%.
После отработки графического движка обычно вызывается плеер музыки. При этом, при традиционном подходе, если отработка графики + музыки не уложилась в кадр до следующего прерывания, то происходит потеря кадра, срыв. Однако в нашем случае происходит следующее. Плеер музыки выполняется с разрешенными прерываниями. Поэтому прерывание останавливает исполнение плеера. Процедура обработки прерывания первым делом, как описано выше, выводит в AY содержимое очередной таблицы. В описываемой ситуации плеер не успел подготовить новые значения (так как его выполнение было прервано). Однако у нас же очередь, в которой ждут готовыми целых 5 таблиц AY! Поэтому процедура обработки прерывания спокойно выводит в AY содержимое следующей таблицы из очереди. После этого она отрабатывает графику. А после отработки графики происходит возврат из прерывания, и прерванный код плеера продолжает работу. Длина очереди AY-таблиц в 5 штук обеспечивает то, что даже при одновременном неблагоприятном стечении обстоятельств (высокая нагрузка на процессор от графики за несколько кадров подряд), не будет нарушена ни плавность анимации, ни воспроизведение музыки. При необходимости длину очереди можно увеличить, благо это не затратно по памяти.
А когда нагрузка на процессор от графического движка уменьшится, то произойдет вызов музыкального плеера сразу несколько раз подряд, что обеспечит восполнение сократившейся очереди воспроизведения. Очередь воспроизведения лучше всего организовывать в виде циклического буфера, аналогично буферу клавиатуры. Пример реализации кода с разрешенными прерываниями (длина очереди - 8):
QUEUE_FULL:
HALT
MAINLOOP:
LD A,(BUF_WR_POS) ;указатель конца очереди (куда добавляются элементы)
LD C,A
INC A
AND 7
LD B,A
LD A,(BUF_RD_POS); указатель начала очереди (откуда извлекаются элементы)
CP B
JR Z,QUEUE_FULL; очередь заполнена - ждать освобождения, что произойдет в процедуре обработки прерывания
; в очереди есть место - вызвать плеер
PUSH BC
CALL MUSIC_PLAY
POP BC
; увеличить указатель записи в очередь
LD A,B ; B - указатель конца очереди после увеличения на 1
LD (BUF_WR_POS),A
JR MAINLOOP
В процедуре обработки прерывания вызывать музыкальный плеер не нужно. Нужно только извлекать очередной элемент из очереди и выводить в AY содержимое таблицы, а также вызывать графический движок:
ISR:
PUSH AF
PUSH BC
LD A,(BUF_WR_POS)
LD C,A
LD A,(BUF_RD_POS)
CP C
JR Z,QUEUE_EMPTY; Очередь пуста - пропустить очередной вывод в AY
; А - место в очереди, откуда следует осуществить AY-вывод
PUSH AF
CALL AY_TRANSFER ; Вывести в регистры AY первый элемент очереди
POP AF
INC A
AND 7
LD (BUF_RD_POS),A
QUEUE_EMPTY:
CALL GRAPHICS_ENGINE
POP BC
POP AF
EI
RET