Музыкальный движок Muse 128b
evilpaul/Ate Bit
Муза - такая штука,которая может внеза─
пно прийти, когда задаёшься сложной зада─
чей. Тут случай был такой: я смотрел 128-
байтные звуковые демонстрации gwEm'ана
Atari ST и решил - я тоже должен попробо─
вать!
Я уже писал несколько музыкальных пле─
йеров для ZX в разные времена. Ещё когда
Ate Bit официально не существовал,мы выпу─
скали пару дем группой evilpaul/ЧMat/Eater
- и они как раз использовали мои первые
попытки в области создания плейеров (я то─
гда даже не знал, что на Спектруме сущест─
вуют трекеры). Шли годы, и мы перешли на
обычный плейер от Vortex Tracker'а, но я
до сих пор пишу плейеры для наших малень─
ких дем. Первый такой был для mipmap (2007
год) - там надо было реализовать в 1k
intro несколько эффектов и музыку, чтобы
превзойти все старые 1k intro. Разумеется,
места для хранения настоящих музыкальных
данных не было, поэтому ноты вытаскивались
прямо из ПЗУ. Главная хитрость была в под─
стройке данных перед проигрыванием,и самый
простой способ для этого - интерпретиро─
вать случайные данные в рамках конкретной
гаммы. Например, так:
0 -> C
1 -> D
2 -> E
3 -> F
4 -> G
5 -> A
6 -> B
7 -> C
8 -> Dи т.д.
Так случайные данные окажутся в тональ─
ности до-мажора, и это довольно работоспо─
собный способ для получения "удовлетвори─
тельно" звучащей музыки из рандомной пос─
ледовательности.Всё,что требуется - табли─
ца высот нот в мажорной гамме, которая мо─
жет быть легко сгенерирована из нескольких
значений. Получается что-то вроде обычного
трекерного плейера, но только мы не зани─
маем место в таблице нот под ноты, которые
мы не используем. Можно даже двинуться
дальше и использовать 5 нот, так называе─
мую пентатонику. Например:
0 -> C
1 -> D
2 -> E
3 -> G
4 -> A
5 -> C
6 -> D
7 -> E
8 -> Gи т.д.
Пентатоника хорошо знакома гитаристам -
каждый, кто пытался импровизировать мело─
дию или бас на гитаре, знает, что если за─
жать на грифе пентатонику, то практически
всё, что играешь, звучит более чем непло─
хо. То же самое происходит с пропусканием
данных из ПЗУ через пентатонику. Если вы
реализовали такое дело, то дальше достато─
чно зациклить, например, 16 байт и поис─
кать хорошие 16-байтные последовательности
в ПЗУ. Это может быть довольно долгий про─
цесс, но по идее, если зациклить что-то
несколько раз, мозг сам воспринимет это
как мелодию. Запомните этот факт, мы к не─
му ещё вернёмся.
Итак, теперь вы можете написать хорошую
мелодию для 1k демы, вообще не храня нот─
ный текст. Или не можете? Есть две вещи,
которые вы не достанете из случайных дан─
ных - инструменты и ударные. Инструменты
хранятся в виде коротких таблиц с громкос─
тями и сдвигами частоты. Громкости управ─
ляют огибающей звука, а сдвиги частоты мо─
гут дать вибрато или при необходимости
другие эффекты типа слайдов или легато.Ли─
нию ударных я обычно пишу вручную. Четыре
паттерна по четыре такта из четырёх нот
каждый, ритмически повторяющийся в неболь─
шим изменением в конце, занимает 64 байта.
Это слишком много для 1k демо, но к счас─
тью, это хорошо пакуется.
Я делал пару 1k дем с таким методом, а
также 1kdj, который использует то же са─
мое, но с нотами, написанными вручную,а не
взятыми из ПЗУ.При этом пришлось пожертво─
вать визуальными эффектами и графикой,зато
там много места под плейер и музыкальные
данные. А однажды в начале 2014 года я пи─
сал новый маленький плейер для 4k демы.
Этот проект развился в демо "Daytrip" (бу─
дет показано на Sundown 2015 ) с большим
музыкальным плейером, который никак не
влезет в 4k. Но у меня было несколько идей
в начале разработки этого демо как раз по
поводу совсем крошечного музыкального пле─
йера. Именно тогда я наткнулся на 128-бай─
тные звуковые демонстрации от gwEm. Ведь
явно что-то похожее можно сделать и на
Спектруме?
Ответ был,разумеется, "да",но для этого
требовалась ещё пара хитростей. Во-первых,
в 128 байтах нет места ни для кода,переко─
дирующего случайные данные, ни для таблицы
нот.Но это не проблема,так как мы уже зна─
ем, что если зациклить случайный набор нот
несколько раз, мозг решит, что это музыка.
Это легко проверить: оцифруйте пару секунд
чего угодно и прокрутите в цикле - в 9
случаях из 10 это сойдёт за перкуссию.
Muse именно так и делает. Она играет один
случайный набор нот, высоких, в качестве
мелодии,а другой набор нот, низких и более
долгих, в качестве баса. Ударные зашиты в
код в виде набора из 4 нот. Эти 4 ноты -
первые 4 байта программы,из них первые три
(SCF,NOP,NOP) ничего не делают,а четвёртый
- это первая настоящая команда в программе
(LD DE,nn). Ударные не шедевр, но занимают
всего три байта.
Другая новая хитрость - в том, что Muse
не синхронизирована с прерываниями.Там да─
же нетHALT. Вместо этого она просто кру─
тится в коротком цикле между тиками и в
конце концов обновляет регистры AY на час─
тоте выше 50 Гц - в результате звук неско─
лько отличается от плейеров,которые играют
на 50 Гц.
Плейер работает с 10-байтной структурой
данных для каждого канала.На эту структуру
указывает регистрIX - плейер обрабатывает
один канал за раз. Регистр микшера в AY
настраивается в начале программы так,чтобы
один из каналов был с включенным шумом -
это будут ударные.
Каждый канал имеет таймер, поэтому ка─
налы играют с разной скоростью - бас мед─
леннее,ударные быстрее.Из значения таймера
ещё вычисляется текущая громкость канала.
Мелодии берутся из ПЗУ, но мы указываем
только СТАРШИЙ байт адреса в данных кана─
ла. С таким ограничением довольно трудно
найти в ПЗУ хорошие паттерны, но опять-
таки - это экономит несколько байтов.
И наконец, как вы увидите в исходнике,
Muse использует несколько байт данных (по
три на каждый канал, плюс один для удар─
ных), которые находятся вне области прог─
раммы. Рассчитано на то,что там изначально
лежат нули.
Вот сам исходник Muse (компилируется
ассемблером pasmo ).
AYCTRL equ 65533
AYDATA equ 49149
org 32768
start
; паттерн ударных лежит по круглому адресу
;и занимает 4 байта
; три байта мы задаём, 4-й берём
;из команды ld de,nn
; не лучший вариант ударных, но
;оставалось только три байта...
drumPattern
db 0x37 ; scf
db 0 ; nop
db 0 ; nop
ld de,%110000+(7<<8);настраиваем
;регистр микшера
calloutAyByte
; -- главный цикл --
loop
ld ix,databass ; указатель на
;данные первого канала
call chanplay
db Oxdd,0x2e,LOW(datatune) ;ld lx
call chanplay
db Oxdd,0x2e,LOW(datadrums);ld lx
call chanplay
synca djnz synca
syncb add hl,sp ;хорошая, медленная,
;однобайтная инструкция...
djnz syncb
jr loop
; -- играть один канал --
chanplay
; обновляем таймер
ld a,(ix+CHA_TIMER)
sub (ix+CHA_SPEED)
ld (ix+CHA_TIMER),a
ld h,a ;запоминаем таймер для
;огибающей громкости ниже
jr nz,nonoteinc
; следующая нота
inc (ix+CHA_LOOPPOS)
ld a,(ix+CHA_LOOPPOS)
and (ix+CHA_REPEATLENGTHMASK)
jr nz,nonewpat
; следующий паттерн
ld a,(ix+CHA_PATTERNDATALOW)
add a,(ix+CHA_PATTERNDATASTEP)
ld (ix+CHA_PATTERNDATALOW),a
nonewpat
nonoteinc
ld d,(ix+CHA_PATTERNDATAHIGH)
ld a,(ix+CHA_LOOPPOS)
and (ix+CHA_LOOPLENGTHMASK)
add a,(ix+CHA_PATTERNDATALOW)
ld e,a
ld a,(de)
ld d,(ix+CHAN_AYCOARSEPITCHCTRL)
ld e,a
call outAyByte;установим частоту
ld b,4
; h=таймер, >>4, чтобы получить громкость
vshift srl h
djnz vshift
ld d,(ix+CHAN_AYVOLCTRL)
ld e,h
; call outAyByte ;установим громкость
; ret
; d = ctrl, e = data
outAyByte
ld bc,AYCTRL
out (c),d
ld b,HIGH(AYDATA)
out (c),e
ret
CHAN_AYCOARSEPITCHCTRL equ 0
CHAN_AYVOLCTRL equ 1
CHA_PATTERNDATALOW equ 100
CHA_PATTERNDATAHIGH equ 2
CHA_TIMER equ 101
CHA_SPEED equ 3
CHA_LOOPPOS equ 102
CHA_LOOPLENGTHMASK equ 4
CHA_REPEATLENGTHMASK equ 5
CHA_PATTERNDATASTEP equ 6
databass
db 5
db 10
db 5
db 1
db %1
db %111
db 64
datatune
db 3
db 9
db 13
db 8
db %1111
db %111111
db 19
datadrums
db 1
db 8
db HIGH(drumPattern)
db 4
db %11
db %11
; db 0
end start
Other articles: