Beepová hudba do hry Blumps

Если вы читаете и западные дисковые журналы, то могли заметить, что к последнему номеру Subliminal Extasy было приложено и демо моей игры Blumps. Поскольку я не музыкант, я попросил z00m‑а сделать beep‑музыку — и здесь поделюсь проблемами, которые из‑за этого у меня возникли.

Z00m выбрал музыкальный трекер 1tracker и музыкальную рутину SquatM от Shiru.

Надоедливый border

Оригинальная музыкальная рутина рисует в border полоски, похожие на те, что в Bytepak‑рутине ZX10. Наверняка помните демо Busy на вступительной кассете к Didaktik M. Это, конечно, красиво, но для титульной музыки в игре совсем не подходит.

Решение очень простое — перед каждым OUT (#FE),A нужно вставить AND 24. Во всей рутине всего два out‑а, так что никаких проблем.

Остановка воспроизведения клавишей

Ещё один минус рутины в том, что она вообще не проверяет нажатие клавиши и крутит своё по кругу. Мелкая ложка дёгтя: рутина использует стек как источник данных, поэтому содержимое стека перед вызовом рутины нужно куда‑то сохранить. Вот изменения, которые добавляют проверку любой клавиши (строки, начинающиеся знаком +, — мои правки): begin + ld (return+1),sp ld hl,music_data call play +return ld sp,0 + ld hl,10072 + exx + ei ret ... sample_no_stop djnz sound_loop0 ld (sample_ptr),hl + xor a + in a,(254) + or %11100000 + inc a + jp nz,return jp play_loop

Out of memory

Отлично, музыка уже играет, не рисует в border и её можно остановить. Встраиваю её в свою игру и… Святое спагетти‑монстр вместе с дуршлагом! Эта музыка занимает 17 кБ!!!

В демоверсию она кое‑как влезла, но в памяти осталось лишь несколько свободных байтов. Что с этим делать?

После первой паники и нескольких дней страданий я успокоился и посмотрел на данные музыки: .pattern: dw #71f,#0,#0,#51a,#0,NOP_2 dw #700 dw #704,#0 dw #704,#917 dw #700 dw #704,#0 dw #704,#a34 ...

На первый взгляд — куча повторяющихся данных! Но так ли это на самом деле? Моё первое впечатление было, что каждая строка данных соответствует строке в трекере. Первый байт в строке — описание строки, за ним следуют дополнительные данные. После небольшого анализа проигрывающей рутины моя теория подтвердилась. После каждой строки паттерна рутина возвращается на начальную метку play_loop. План, стало быть, — сделать таблицу нот (нота = строка из паттерна), которая будет содержать адреса данных отдельных нот. После правки данные должны выглядеть так:

.pattern: dw NOTE_001 dw NOTE_002 dw NOTE_003 dw NOTE_004 dw NOTE_002 dw NOTE_003 dw NOTE_005 ... NOTE_001 dw #71f,#0,#0,#51a,#0,NOP_2 NOTE_002 dw #700 NOTE_003 dw #704,#0 NOTE_004 dw #704,#917 NOTE_005 dw #704,#a34 ...

Данных много, поэтому работу нужно автоматизировать. На этом шаге понадобится Вспомогательный Калькулятор (PC). Я подготовил скрипт на Perl, который сделает это за меня. Данные музыки — то есть всё от метки .pattern до конца файла — нужно сохранить в файл data.asm, а скрипт выведет упакованные данные в stdout. Там есть маленькая ошибка: скрипт генерирует метку .pattern дважды, но свою задачу выполняет, а одну лишнюю строку вы наверняка с радостью уберёте вручную. Скрипт приложен к дискмагу, и если хотите распечатать его на принтере, можно через команды:

+3DOS: COPY "SCRIPT.PL" TO LPRINT D40/80: LPRINT *"script" TRDOS: RANDOMIZE USR 15619: REM: LOAD "script" MBDOS: LOAD "script"

В +3DOS и D40 скрипт лежит в текстовом виде. В TRDOS и MBDOS — basic‑программа, которая печатает скрипт на экран, но номер канала можно поправить в 10‑й строке. TRDOS, правда, поддерживает последовательные файлы, но, увы, не умеет читать то, что в них сохранил. MBDOS — отдельная история.

Посмотрим ещё на изменения, которые позволяют проигрывать упакованные данные:

... play_loop

pattern_ptr=$+1 ld sp,0 + pop hl + ld (pattern_ptr),sp + ld sp,hl ...

Здесь мы достаём адрес «ноты» и сохраняем новое значение SP для следующего прохода рутины. Затем SP ставим на данные «ноты», как того ожидает остальная рутина. ... loop_ptr=$+1

  • ld sp,0 + ld hl,0 + ld (pattern_ptr),hl + jr play_loop

jp return_loop ...

Оригинальная рутина адрес, где музыка зацикливается, выставляет только в регистр SP — ей этого достаточно, потому что позже в рутине она перезаписывает адрес в pattern_ptr. Мы же перезаписываем этот адрес сразу и вместо return_loop возвращаемся на play_loop. ... skip_percussion + ;ld (pattern_ptr),sp ... А здесь запрещаем перезапись текущей позиции, потому что мы движемся по своим изменённым данным.

Полный исходник найдёте в приложении и можете посмотреть через:

+3DOS: COPY "SQUATM.ASM" TO SCREEN$ D40/80: PRINT *"squatm" TRDOS: RANDOMIZE USR 15619: REM: LOAD "squatm" MBDOS: LOAD "squatm"

И каков результат моих стараний? Общая длина музыки уменьшилась с 17 кБ до неполных 9 кБ! Это почти вдвое меньше. В исходной музыке 2498 строк, а после правки данные сжались до 421 уникальной строки. При этом качество воспроизведения сохранилось — моё нетренированное ухо разницы вообще не слышит.

Спасибо z00m‑у за отличную музыку, и если в слабый момент я, не дай бог, произнёс в его адрес какое‑нибудь уничижительное словечко — глубоко извиняюсь.

mike/zeroteam

Share your thoughts about the article