Сегодня — экстремальный geek out: максимально узкоспециальная тема с запутанным кодом на ассемблере Z80. Раскроем секреты Тима Фоллина в «биперной» музыке на Sinclair ZX Spectrum 48K, попытаемся повторить, а может быть и превзойти его достижения. Некогда объяснять, разберёмся по ходу кода!

▍ Контекст


Есть такой популярный британский домашний компьютер 1982 года выпуска — Sinclair ZX Spectrum. Его аппаратные клоны и игры были очень популярны в странах бывшего СССР и других развивающихся странах (от Польши до Бразилии), довольно значительным образом повлияв на компьютеризацию населения в этой местности. С тех пор по всему миру осталось множество бывших пользователей, ностальгирующих по этой платформе, её играм, графике и даже музыке.

Оригинальный ZX Spectrum 48K (фото Nico Kaiser)

У оригинальной модели ZX Spectrum нет вообще никакого аппаратного синтезатора звука, или так называемого звукового чипа. У него есть только бипер — простейший динамик, подключённый непосредственно к однобитному порту вывода. Но есть игры, и какие-то звуки и музыка в этих играх есть. В некоторых даже настолько неплохие, что смогли запасть в душу игрокам, и оставаться любимыми даже спустя 30 с лишним лет.

Вместо звукового чипа, звук на ZX Spectrum генерируется полностью программно, кодом, написанным на ассемблере его центрального процессора Zilog Z80, с точным расчётом времени выполнения, буквально по тактам — так называемыми биперными движками. Несмотря на примитивность этой технологии, аналогичной использовавшейся в первых экспериментах по компьютерному синтезу звука в 1950-х на компьютерах типа TX-0 и PDP-1, талант энтузиастов позволил достичь интересных результатов.

Плата ZX Spectrum. Справа внизу виден динамик

Бипер довольно быстро потерял актуальность, так как следующая модель ZX Spectrum была оснащена уже нормальным для тех лет звуковым чипом, а прогресс продолжал шагать семимильными шагами, и вскоре устарела и сама платформа, и 8-битные архитектуры в принципе. Таким образом, полный потенциал технологии программного синтеза звука раскрыт в то время не был.

И хотя платформа ZX Spectrum давно перестала быть массовой, энтузиасты остались — теперь это ретрокомпьютинг. В этой среде в начале 2000-х годов возник новый интерес к биперу, появились новые разработки. Ведь даже по прошествии многих лет кому-то всё ещё не давали покоя лавры Тима нашего Фоллина. Кому-то — это, например, мне.

▍ Импульсный синтез


Один из самых популярных и любимых слушателями способов сверх-минималистичного синтеза многоканального звука — метод сверх-коротких импульсов, так называемых «иголок» (pin), или «импульсный» синтез.

Именно он придаёт то самое звучание музыкальным композициям Тима Фоллина (игры Agent-X, Chronos, Vectron), Кейта Тинмана (Cabal, Firefly, Midnight Resistance), Дэвида Уиттакера (ATV Simulator, Beyond the Ice Palace, Trantor). Практически концентрированная однобитная звуковая ностальгия для тех, кто застал ZX Spectrum и игры на кассетах.

Характерное звучание импульсных биперных движков — тихое, «тонкое», дребезжащее и звенящее, но обладающее гармониками, напоминающими звук перегруженной электрогитары и предположительно приятными слуху любителей рок-музыки. У кого-то, впрочем, это звучание вызывает кровотечение из ушей. Это совершенно нормальный побочный эффект, не стоит беспокоиться.

Такой способ, конечно, родился не ради своего своеобразного звучания. Это просто хоть что-то, похожее на полифонию, что чудом удалось выжать из имеющихся ресурсов настолько древних машин — мощности их 8-битных микропроцессоров едва-едва хватало для одновременной программной генерации нескольких сигналов разной частоты и условной громкости.

Достоверно неизвестно, кто придумал и впервые реализовал импульсный синтез, но его корни прослеживаются до Music System 1977 года от Processor Software для компьютера Sol-20 и похожих Альтаир-совместимых машин с шиной S-100. Эта система позволяла программно синтезировать трёхголосую полифонию с тем самым характерным звучанием, но ещё без плавного затухания.

К слову, позже эта программа под названием Музыкальная Система была адаптирована для советских компьютеров на базе микропроцессора КР580ВМ80, типа Радио 86РК, и, видимо, именно это стало причиной такого странного подключения динамика: к сигналу разрешения прерывания (такое решение было на компьютерах с шиной S-100).

Но действительно широкое распространение импульсный синтез получил во времена коммерческих игр для ZX Spectrum и Apple II, в 1984-1989 годах. На ZX Spectrum этот звук можно услышать в великом множестве игр, в частности, в играх Code Masters и SpecialFX Software, а на Apple II нечто подобное звучит в оригинальном Prince of Persia и Factor Seven (со звуковыми процедурами Кайла Фримена).

Ну а самым главным мастером этого дела стал Тим Фоллин — легенда чиптюна, автор невероятной музыки в огромном количестве игр на множестве 8 и 16-битных платформ. Начав заниматься компьютерным звуком в 15 лет, свои первые успехи он совершал на ZX Spectrum, поражая воображение слушателей иллюзией крутейшей прог-роковой музыкальной композиции, скрытой в вое пылесоса, раздающегося из маленького динамика.


Тим сам разрабатывал код биперных движков и потом сам же писал для них музыку, поэтому он смог выйти за рамки возможного, и повысить полифонию сначала до трёх каналов (Vectron), потом до четырёх (Future Games), и даже до пяти (Chronos). Это было достигнуто ценой серьёзных компромиссов, но до сих пор его работы являются своего рода Святым Граалем среди любителей биперной музыки: вершиной и технического, и композиторского мастерства.

▍ Как это работает


Как известно, для генерации простейшей «квадратной» звуковой волны, так называемого меандра, нужно менять уровень выходного сигнала дважды за его период, через равные промежутки времени. Длительность периода определяет частоту получаемого звука.

Классическая квадратная волна, две разные частоты и две разные скважности

Если промежутки времени внутри периода становятся неравными, изменяется скважность и спектр гармоник. На слух это воспринимается как более «тонкий» звук, чем больше скважность, тем тоньше и тише. Это качество полезно для музыкальных целей, чтобы разнообразить звучание, и часто применяется в звуковых синтезаторах. Например, в игровой консоли Famicom (Денди, NES) реализовано три скважности: 50%, 25% и 12.5%, а в звуковом синтезаторе компьютера Commodore 64 — аж 4096 градаций скважности, что позволяет создавать замечательные модуляционные эффекты.

Помимо звуковых применений, изменение скважности лежит в основе метода широтно-импульсной модуляции для управления мощностью электрических устройств. При этом частота генерируемого сигнала не изменяется и обычно лежит выше предела слышимости, в районе десятков и сотен килогерц, а меняется только ширина импульсов. Чем меньше скважность (шире импульсы), тем больше энергии передаётся потребителю.

ШИМ разной скважности, проценты соответствуют количеству передаваемой потребителю энергии

Метод импульсного синтеза сочетает оба этих принципа, но несколько нестандартным образом. В начале каждого периода сигнала генерируется очень короткий импульс, шириной буквально в доли процента от периода. Несмотря на такую краткость, на слух это всё ещё ощущается тоном заданной частоты, а чем меньше длина импульса, тем меньше воспринимаемая громкость.

При этом импульсный синтез не является ШИМ. Это скорее частотно-импульсная модуляция, так как в зависимости от нот в музыкальной композиции меняется только частота, но не ширина импульсов — их длина остаётся одинаковой и зависит только от «громкости». С этой особенностью связан недостаток импульсных движков: недостаток низких частот.

Импульсный синтез: разные частоты, одинаковая «громкость»

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

Фрагмент трека из игры Agent X. Указатель установлен на ударный инструмент — это буквально один период волны низкой частоты

Помимо тональных каналов, импульсные движки часто обладают минималистичной перкуссией, звуки которой прерывают работу всех каналов тона на короткий период времени — так как звук удара громкий, краткое исчезновение тональной составляющей композиции почти незаметно на слух. Поэтому ударные инструменты в биперной музыке обычно очень короткие, отрывистые, вплоть до простых щелчков, как в музыке Фоллина.

▍ Реализация на Z80


Тим Фоллин лично делится своими секретами на страницах журнала Your Sinclair в номере за август 1987 года

Типичная реализация импульсного синтеза на микропроцессоре Z80 такова.
Есть основной цикл генерации звука, имеющий фиксированное время выполнения — оно определяет базовую частоту.

Есть набор 8-битные счётчиков-делителей, по числу канала. Например, два. Каждую итерацию основного цикла каждый счётчик декрементируется. Если счётчик достиг нуля, он заново загружается значением, соответствующим генерируемой частоте, а также генерируется очень короткий импульс-иголка.

Таким образом счётчики делят базовую частоту на значение от 1 до 255, получая 255 возможных частот, производных от базовой.

В псевдокоде это выглядит так:

цикл:

   счётчик1--
   если счётчик1 == 0
      счётчик1 = N
      генерация импульса1

   счётчик2--
   если счётчик2 == 0
      счётчик2 = N
       генерация импульса2

   переход на цикл

Длительность генерируемого переполнением счётчиков импульса дольше, чем время основного цикла. Сам импульс состоит из двух частей: сначала порт бипера устанавливается в 1, проходит некоторое время, от которого зависит ощущаемая громкость канала, потом порт устанавливается в 0, и проходит оставшееся время импульса. Это необходимо для того, чтобы псевдо-громкость не влияла на генерируемую частоту.

В псевдокоде генерация импульса выглядит следующим образом:

длительность1 = текущая громкость канала
длительность2 = максимальная громкость - текущая громкость канала

бипер = 1

while длительность1 > 0
   длительность 1--

бипер = 0

while длительность2 > 0
   длительность 2--

Во время генерации импульса счётчики-делители не работают, это приводит к кратковременной расстройке генерируемой частоты, но так как импульсы относительно редки, особенно на низких частотах, они не сильно влияют на среднее время, что обеспечивает какую-никакую стабильность строя при малом числе каналов.

Опуская несущественные детали, реализация счётчика и выдачи импульса Z80 для одного канала на ассемблере выглядит примерно так:

;счётчик канала

    dec d			;декремент счётчика
    jp nz,...		;пропуск ветки, если переполнения не случилось
    ld d,_F			;перезагрузка счётчика

;генерация импульса

    ld a,_D1		;время первой задержки (неактивная часть импульса)
.delay1
    dec a			;цикл первой задержки
    jr nz,.delay1		;переход на цикл
    ld a,_O			;16 для активного канала, 0 для полного заглушения
    out (#fe),a		;вывод в порт
    ld a,_D2		;время второй задержки (активная часть импульса)
.delay2
    dec a			;цикл второй задержки
    jrt nz,.delay2		;переход на цикл
    out (#fe),a		;после цикла A=0, вывод в порт

Для установки фактических значений _F, _D1, _D2 и _O используется самомодифицирующийся код, то есть значения записываются кодом обвязки прямо в соответствующие поля команды. Это широко распространённая практика в биперных движках, так как для стандартной загрузки не хватает ни свободных регистров, ни тактов процессора.

У этой реализации есть несколько проблем.

Во-первых, 8-битные счётчики дают малое количество возможных генерируемых частот, и для подобных движков характерен общий нестрой, усиливающийся в верхнем диапазоне: ноты воспроизводятся не точными частотами, а с очень грубым приближением.

Во-вторых, если одновременно переполнились счётчики нескольких каналов, генерируется несколько импульсов, что может очень сильно изменять среднее время выполнения цикла. Это вносит дополнительную слышимую расстройку частоты. Она хорошо заметна даже при двух каналах, если в одном из них или в обоих звучат достаточно высокие ноты, поэтому большинство движков ограничиваются лишь двухголосой полифонией.

Многоканальные же движки Тима Фоллина были настолько подвержены кросс-расстройке каналов, что каждое сочетание нот в треке требовало подбора каждого из делителей, чтобы попасть в правильные ноты. И именно это, помимо прочих нетривиальных ограничений, не позволило до сих пор создать удобный редактор для классических движков Фоллина. Сам же автор делал это вручную, набирая данные музыки прямо в ассемблерном коде и на слух, подбирая нужные компенсации частот.

▍ Бипер, я и QChan


В 2009 году, вдохновившись творчеством небезызвестного Mister BEEP’а, я плотно заинтересовался темой биперной музыки. Как известно, ограничения провоцируют креативность, и этот крайне специфический формат неожиданным образом позволил мне найти оптимальный баланс между технической и музыкальной составляющими в собственном творчестве.

Удобных инструментов для создания биперной музыки тогда не было, и поначалу я перерабатывал уже существующие музыкальные редакторы прошлых лет под более современный трекерный интерфейс. К 2010 году, накопив некоторый опыт, я начал изобретать и свои собственные движки. Я пробовал различные подходы и искал новые идеи, которые позволили бы реализовать больше каналов, более чистое и стабильное звучание, чем было достигнуто в разработках прошлых лет.

Мне сходу удалось создать крайне удачный движок Phaser1, полюбившийся многим чиптюн-композиторам, а годом позже ещё два популярных движка — Tritone и Octode. Они основаны на других принципах, и в целом это уже совсем другая история. Главное, что в их тени осталась ещё одна разработка того периода.

Во время торфяных пожаров августа 2010 года в дикой жаре и натуральном Сайлент Хилле за окном в попытках улучшить идеи движка музыкального редактора Music Synth 48K (используется в игре Ano Gaia) родилась идея.

Сначала она была реализована в двухканальном движке Stocker — продвинутом аналоге Music Synth 48K, обладающем развитой системой огибающих и импортом нотного текста из Vortex Tracker.

Практически сразу же после этого я решил пойти дальше и посягнуть на лавры Тима нашего Фоллина. Так родился четырёхканальный QChan.

Stocker и QChan используют вариацию импульсного синтеза, отличающуюся другим набором компромиссов, и, как следствие, кардинально другой реализацией, имеющей свои преимущества и недостатки.

Во-первых, вместо 8-битных счётчиков-делителей используются 16-битные аккумуляторы, что улучшило ситуацию с точностью настройки отдельных нот.

Счётчики и аккумуляторы — это два основных принципа организации частотных делителей в простых синтезаторах звука:

  • Счётчик — целочисленная переменная, которая декрементируется до обнуления и перезагружается заново. В момент перезагрузки инвертируется хранимое отдельно состояние выходного сигнала.
  • Аккумулятор — целочисленная переменная более высокой разрядности, к которой прибавляется некоторое константное значение, пока разрядность счётчика не переполнится. При такой схеме перезагрузка не требуется, а выходной сигнал можно просто брать из старшего бита (и не только, все старшие биты — готовая к употреблению скважность сигнала).

Эти решения встречаются и в программном синтезе, и в железных звуковых чипах. У них есть свои плюсы и минусы, они лучше или хуже подходят тем или иным ситуациям, и в случае с QChan метод аккумулятора пришёлся к месту.

Во-вторых, в одну итерацию общего цикла может генерироваться только один импульс, даже если одновременно переполнились аккумуляторы нескольких каналов. Длительность цикла поддерживается более-менее стабильной: импульс генерируется каждую итерацию, меняется только длительность его активной части, и это обеспечивает устойчивость общего строя. Некоторая нестабильность всё же остаётся, он не полностью выверен по времени выполнения, но это не приводит к значительным отклонениям, ощущаемым на слух.

Недостатком получившегося на этих принципах нового движка стала очень большая длительность цикла, то есть низкая частота дискретизации, порядка 8.6 кГц, а также очень «тонкий» и довольно тихий звук, ещё тоньше и тише, чем у Фоллина. Одно связано с другим: увеличить громкость можно только удлинением импульса, но тогда вырастет время выполнения цикла, и диапазон воспроизводимых частот уйдёт из приемлемого на практике.

Другой недостаток движка — наличие специфической интерференции между каналами. Дело в том, что длительность импульсов не складывается арифметически, а накладывается по OR. Это сделано ради скорости: честное сложение занимает больше времени и приводит к переполнению максимальной длительности импульса, что в свою очередь ведёт или к расстройке, или к иным артефактам в звучании.

Но эти недостатки также стали и характерными особенностями движка, и как оказалось, специфичная для них звуковая окраска тоже имеет своё применение и привлекательность.

Разберём код основного цикла генерации звука, все четыре канала, без купюр:

soundLoop

    add ix,de	;аккумулятор первого канала
    sbc a,a		;трюк: превращаем признак переноса в значение 0 или 255
.vol0=$+1
    and 0		;накладываем громкость по AND и получаем 0 или громкость
    ld b,a		;в B хранится длительность импульса для текущей итерации цикла
    add hl,sp	;аккумулятор второго канала
    sbc a,a		;всё то же самое
.vol1=$+1
    and 0
    or b		;складываем длительности по OR
    ld b,a		;запоминаем обратно в B
    exx		;регистры кончились, включаем альтернативный набор
    add iy,de	;аккумулятор третьего канала
    sbc a,a
.vol2=$+1
    and 0
    exx		;возвращаем набор, чтобы сохранить длительность импульса
    or b
    ld b,a
    exx		;опять альтернативный набор
    add hl,bc	;аккумулятор четвёртого канала
    sbc a,a
.vol3=$+1
    and 0
    exx		;опять возвращаем основной набор
    or b		;накладываем длительность, это также обновляет флаг Z
    jr z,.noOut	;если длительность нулевая, пропускаем генерацию активной части импульса
    ld b,a		;загружаем счётчик цикла. гарантированно ненулевой
    ld a,16		;устанавливаем бит бипера
    out (#fe),a	;выводим в порт
    ld a,b		;возвращаем длительность A, она нужна дальше
    djnz $		;цикл задержки для активной части импульса
    cpl		;инвертируем A, чтобы получить длительность неактивной части импульса
.noOut
    add a,17	;прибавляем к предыдущей длительности 17. Если она была 0, станет 17, а если была ненулевой, в результате CPL получается вычитание
    ld b,a		;загружаем счётчик цикла, снова гарантированно ненулевой
    xor a		;сбрасываем бит бипера
    out (#fe),a	;выводим в порт
    djnz $		;цикл задержки для неактивной части импульса
    dec c		;счётчик итераций основного цикла
    jp nz,soundLoop	;выполняем основной цикл

Сразу становится ясно, почему канала четыре (и отсюда же название движка, Q от Quad) — на большее просто не хватило регистров. Каждый канал требует две 16-битные пары, которые можно складывать одной командой. Это пары IX и DE,HL и SP (указатель стека тоже задействован для счётчика), IY и DE’, HL’ и BC’. Оставшиеся три регистра, A, B и C, применяются для генерации импульса и организации цикла.

▍ Реновация


Несмотря на некоторые достоинства, движок QChan оказался довольно сложным для освоения инструментом, особенно на первых порах: тогда никаких редакторов ещё не было, и музыку для него нужно было писать в формате XM-модуля, а потом конвертировать прилагающейся утилитой для командной строки.

Со временем появилось ещё два способа создания музыки — поддержка в редакторе Beepola и поддержка в моём 1tracker. Но и более удобными инструментами нашлось не так уж много композиторов, способных раскрыть потенциал движка и обратить его особенности себе на пользу.

Главным образом это британец Рич Холлинс, известный как AtariTufty, который более десяти лет регулярно создаёт новые музыкальные композиции с использованием QChan. И хотя Tufty никогда не жаловался на нехватку возможностей, настало время открыть новые грани его творчества, обновив движок.

Опыт многих предыдущих разработок в этой области подсказал список действительно полезных на практике возможностей, которые стоило бы добавить:

  • Тонкая настройка частоты нот, для каждой в отдельности.
  • Не только спад, но и нарастание громкости.
  • Произвольное назначение громкостей и скорости спада или нарастания каждой ноте в отдельности.
  • Возможность старта ноты с перезапуском огибающей или без него, для каждой ноты в отдельности (режим легато).
  • Сэмплы для ударных разной длительности с компенсацией темпа.
  • Какой-никакой слайд тона для каналов.
  • Более тонкий контроль темпа.

В основном реализация этих улучшений относится к внешней обвязке движка: парсеру музыкальных данных и кадровой логике: в оригинальном QChan нельзя было настраивать ноты, баланс «громкостей» задавался только глобально для композиции, из огибающих было только затухание, и оно устанавливалось для отрезков, а не каждой ноты.

Я долго ломал голову над тем, что бы такое можно было доработать в коде синтеза тональных каналов, но так и не смог придумать действительно значимых улучшений. Всё-таки эта идея была достаточно хорошо проработана ещё во времена её начальной реализации.

Но если нельзя сделать лучше, можно сделать хуже! Исходя из опыта прошлых лет я решил действовать смелее, не столь строго придерживаться точных расчётов, и в порядке эксперимента сделал вариацию движка с ещё менее стабильным временем цикла. Это вполне сработало: расстройка не такая критичная, так как переполнение каждого аккумулятора прибавляет только семь тактов к общему времени цикла, и как оказалось, это не очень существенная разница по сравнению с длительностью импульса, чтобы вызывать значительную расстройку.

За счёт этого хака удалось повысить скорость основного цикла, а значит, получить более точный контроль частоты генерируемого звука. Теперь в случае, когда импульса нет (большую часть времени) цикл занимает 357 тактов вместо прежних 404 тактов, что даёт частоту дискретизации уже 9.8 кГц вместо оригинальных 8.6 кГц, и это вполне может считаться хоть и небольшим, но улучшением. Пустячок, но приятно.

Можно было пойти дальше и сделать ещё хуже: добавить к только что достигнутым 357 тактам ещё 7+4 тактов пустых операций и довести время цикла до 368 тактов. И я попробовал это сделать. Дело в том, что в оригинальном ZX Spectrum из-за особенностей схемотехники вывод в порт бипера выравнивается на 8 тактов, и для некоторых биперных движков несоблюдение этой размерности приводит к сильным артефактам (алиасинг, вырождающийся в шум) в звуке. Эксперимент показал, что QChan не подвержен этой напасти: проверка не показала заметной на слух разницы, и данное ухудшение я фиксировать не стал.

Что касается частотных слайдов, разумным компромиссом оказался опциональный слайд всего для одного канала: это лучше, чем отсутствие слайдов, и не так раздувает код обвязки. Всё-таки слайд достаточно редко используемый эффект, чтобы допустить перманентное снижение общего качества звучания ради обеспечения возможности слайдов на всех четырёх каналов.

Я обдумывал возможность внедрения полноценного канала сэмплов для перкуссии, работающего без прерывания тональных каналов, подобно моим движкам серии Squat. Но похоже, что в существующий дизайн цикла генерации звука никак не внедрить, как минимум потому что совершенно нет свободных регистров. Поэтому реализованы только классические прерывающие ударные.

Фрагмент теста QChan2. Видно два сэмпла ударных инструментов, прерывающих импульсы тональных каналов

У меня уже есть готовый универсальный код для подобной перкуссии, ранее применённый в движке Ear Shaver EX. Он уже обладает множеством полезных возможностей: грубая регулировка громкости, смещение начала сэмпла, и даже некоторое подобие фильтра, огрубляющего звучание. Его я и внедрил в новый движок. Ожидалась возможная проблема перекоса баланса громкости между довольно тихими каналами тона и громкими ударными, но удивительным образом баланс сразу совпал очень хорошо и не потребовал доработок.

Новую версию движка я решил назвать самым очевидным образом: QChan24.

▍ Формат


Новые возможности потребовали изобретения нового формата музыкальных данных.

Оригинальный формат, ориентированный на конверсию нотного текста из XM-модулей, использовал систему паттернов и имел множество специфических ограничений. В том числе ноты задавались их номером в таблице, а не непосредственным значением делителя, что не позволяло смещать их частоту и получать эффекты, основанные на интерференции фаз двух сигналов. Но у этого решения был и несомненный плюс: относительная компактность музыкальных данных.

Новый формат, с одной стороны, должен быть достаточно компактным, чтобы треки хоть как-нибудь помещались в память компьютера, и желательно даже в реальных играх. С другой стороны, нужно вместить все новые возможности и при этом обеспечить достаточно быстрый парсинг данных.

Принцип синтеза звука в QChan более-менее располагает с относительно медленному парсингу. Но он всё же не настолько нетребовательный, как в более «громких» движках на принципе Squeeker и Squat (с гораздо более широкими импульсами), в которых можно проводить вне цикла генерации звука очень значительное время без сильного влияния на звучание.

Я решил применить уже давно найденный компромиссный вариант со словарём строк. Все уникальные строки нотного текста помещаются в словарь, а трек представляет собой словарь строк и список указателей на эти строки. Далеко не самый экономичный вариант, но при таком количестве передаваемых параметров он обеспечивает неплохой баланс между объёмом данных и скоростью парсинга.

Также я использовал организацию данных в 16-битных словах — практика, введённая utz в его мощнейших биперных разработках. У этого способа есть большое преимущество: можно читать данные трека стеком, устанавливая SP на начало нужных данных и получая байтовые пары операцией POP за 10 тактов (очень быстро). Таким образом читаются и ссылки на строки, и содержимое самих строк. В том числе можно читать пару AF, сразу занося значения в регистр флагов (у меня это свойство не задействовано, но в других случаях оно крайне полезно).

Строки паттерна состоят из последовательности от одного до нескольких 16-битных слов. Их количество зависит от содержания конкретной строки. Первая байтовая пара содержит длительность строки и флаги последующих данных.

Длительность строки — это количество «кадров» в ней. Оно зависит от настройки скорости трека, но также меняется в зависимости от звучащей в этой строке перкуссии: длительность строки компенсируется на длительность сэмпла, чтобы поддерживать стабильный темп.

Флаги — просто битовые поля, указывающие на наличие данных каждого из тональных каналов, слайда и перкуссии. Если бит установлен, далее в данных будут представлены соответствующие ему 16-битные слова с параметрами, в порядке следования флагов.

Если установлен флаг тонального канала, для него в данных присутствует следующий пакет из одного или двух слов.

Первое слово — слагаемое для аккумулятора, определяющее частоту звука. Старший бит слова используется под режим легато, то есть смену частоты без перезапуска огибающей. Если это слово равно 0, канал заглушается, и второе слово в данных не представлено.

Второе слово — настройки огибающей. Первый его байт содержит скорость огибающей в формате битовой маски (1,3,7,15,31,63,127,255). Второй байт содержит «громкость» 0..15 и два бита режима огибающей: 00 атака с нуля до заданной громкости, 01 спад с заданной громкости до нуля, 10 и 11 удержание заданной громкости.

Если установлен флаг слайда, в данных будет слово со знаковым значением направления и скорости слайда.

Если установлен флаг перкуссии, в данных будет абсолютный указатель на описание сэмпла, в котором указаны его настройки и прочие необходимые данные.

▍ Новый код


Разберём пару интересных моментов в коде нового движка.

Прежде всего, это новый цикл генерации звука тональных каналов, который стал проще прежнего. Главное его отличие в способе накопления длительности импульса при переполнении аккумуляторов канала. Также укорочена общая длительность неактивной части импульса за счёт применения честной смены знака числа (CPL:INC A).

soundFramePlay

   ;регистр A всегда установлен в 0 перед началом цикла и в конце цикла
   ;громкость (длина активной части импульса) теперь накапливается в нём, а не в B

    add ix,de		;аккумулятор первого канала
    jp nc,.noPulse0 	;переход происходит часто, JP быстрее, чем JR
.vol0=$+1
    or 0			;если произошло переполнение, накладываем громкость
.noPulse0
    add hl,sp 		;аккумулятор второго канала
    jp nc,.noPulse1
.vol1=$+1
    or 0
.noPulse1    
    exx			;альтернативные регистры
    add iy,de		;аккумулятор третьего канала
    jp nc,.noPulse2
.vol2=$+1
    or 0
.noPulse2
    add hl,bc		;аккумулятор четвёртого канала
    jp nc,.noPulse3
.vol3=$+1
    or 0
.noPulse3
    exx			;возвращаемся к нормальным регистрам
    or a 			;проверяем длительность импульса
    jp z,.noOut		;если нулевая, пропускаем активную часть
    ld b,a 			;загружаем счётчик цикла, гарантированно ненулевой
    ld a,16			;устанавливаем бит бипера
    out (#fe),a
    ld a,b			;восстанавливаем A
    djnz $			;цикл задержки для активной части импульса
    cpl			;меняем знак длительности
    inc a
.noOut
    add a,16		;считаем длительность неактивной части импульса
    ld b,a			;загружаем счётчик цикла, гарантированно ненулевой
    xor a			;сбрасываем бит бипера
    out (#fe),a
    djnz $			;цикл задержки для неактивной части импульса
    dec c			;счётчик итераций основного цикла
    jp nz,soundFramePlay 	;выполняем основной цикл

Также достаточно интересный момент заключается в реализации огибающих громкости, которых теперь три вида: нарастание, спад и удержание. Этот код работает в логике «кадра», количество которых прямо связано с темпом проигрывания: какая цифра темпа установлена, столько кадров прозвучит за одну строку нотного текста.

.ch0envMask=$+1
    and 0			;накладываем маску делителя на счётчик кадров по AND
    jr nz,.ch0eskip 	;пропускаем логику огибающей, если не 0
    ld hl,soundFramePlay.vol0	;указатель на байт с громкостью в основном цикле
    ld a,(hl)			;получаем байт
.ch0envVol=$+1
    cp 0			;сравниваем с целевой громкостью
    jr z,.ch0eskip		;если достигнута, пропускаем следующую операцию
.ch0envOp=$
    dec (hl)			;изменяем громкость
.ch0eskip

Скорость огибающей задаётся маской, которая складывается по AND с постоянно работающим счётчиком кадров. Шаг огибающей случается, когда полученное значение равно нулю. То есть при маске в 1 спад происходит каждые два кадра, при маске 3 — каждые четыре и так далее. Это хак для сокращения времени выполнения кода, обеспечивающий достаточную гибкость в выборе скорости огибающей.

Для обновления громкости огибающей используется самомодифицирующийся код. Внешней обвязкой проигрывателя подменяется граничное значение громкости в операции CP, а также устанавливается операция INC (HL), DEC (HL) или NOP для режима атаки, спада и удержания соответственно.

▍ Пятая нога


Найденные компромиссы для кода, позволившие сократить время работы цикла, навели на мысль ещё раз попытаться увеличить количество каналов. Но как это сделать, если свободных регистров совершенно не осталось? Выход есть: нужно немного изменить принцип работы.

Вместо двух регистровых пар на канал, будем использовать по одной, только для аккумулятора, а добавляемое значение хранить в самомодифицирующемся коде. Это сильно медленнее, и регистров всё равно маловато. Но парочка всё же высвобождается, и это позволяет добавить пятый канал при длительности основного цикла 436 тактов — незначительное ухудшение качества звука и частотного диапазона.

Эту вариацию движка с отличающимся от четырёхканальной версии кодом синтеза, но очень похожей обвязкой я назвал QChan25:

soundFramePlay

    ;A всегда равен 0

.add0=$+1
    ld de,0			;слагаемое для первого канала
    add ix,de		;аккумулятор первого канала
    jp nc,.noPulse0	;если нет переполнения, пропускаем
.vol0=$+1
    or 0			;накладываем громкость
.noPulse0
.add1=$+1
    ld de,0			;слагаемое для второго канала
    add hl,de		;аккумулятор второго канала
    jp nc,.noPulse1
.vol1=$+1
    or 0
.noPulse1    
    exx			;меняем набор регистров
.add2=$+1
    ld bc,0			;слагаемое для третьего канала
    add iy,bc		;аккумулятор третьего канала
    jp nc,.noPulse2
.vol2=$+1
    or 0
.noPulse2
.add3=$+1
    ld bc,0			;слагаемое для четвёртого канала
    add hl,bc		;аккумулятор четвёртого канала
    jp nc,.noPulse3
.vol3=$+1
    or 0
.noPulse3
    ex de,hl		;берём аккумулятор пятого канала из DE’
.add4=$+1
    ld bc,0			;слагаемое для пятого канала
    add hl,bc		;аккумулятор пятого канала
    jp nc,.noPulse4
.vol4=$+1
    or 0
.noPulse4
    ex de,hl		;возвращаем аккумулятор пятого канала в DE’
    exx			;возвращаем набор регистров

;далее генерация импульса как в четырёхканальной версии

    or a
    jp z,.noOut
   …

▍ Редактор


Очень важной составляющей подобных разработок являются средства создания для них музыки. Ведь сейчас мало кто способен писать хексы в голом коде, как легенды прошлого. Людей без технических знаний, но интересующихся чиптюном и прочей подобной музыкой значительно больше, чем тех, кто готов собирать какой-то код ассемблером, и если дать этим энтузиастам удобные инструменты для работы, они смогут создавать шедевры и без глубокого понимания технической части — порой открывая такие её качества, о которых совершенно не подозревал её разработчик (со мной случалось не раз).

Сейчас мы уже не делаем конвертеры из XM и других форматов, которые тоже требуют определённых технических знаний для использования. По возможности новые движки получают поддержку в моём универсальном редакторе 1tracker, о котором я уже рассказывал на Хабре. Это не всегда простое дело, так как движки постоянно усложняются, и осталось ещё приличное количество разработок недавнего времени, у которых до сих пор такой поддержки нет, по тем или иным причинам — а значит, они остаются недоступными для музыкантов. Поэтому критически важно реализовать поддержку как можно раньше, иначе это получается работа «в стол».

Я не буду вдаваться в детали реализации, но коротко опишу, как выглядит реализация этой поддержки, и как выглядит новый движок для пользователя-музыканта.

Новые движки добавляются в трекер в виде скрипта на AngelScript, размещаемого в текстовом файле с расширением 1te, в котором реализовано несколько методов API редактора. Туда же включается исходник движка на ассемблере Z80 в виде простого текста, так что всегда можно оперативно подкрутить что-нибудь в его коде.

Методы скрипта конфигурируют интерфейс трекера, формируя нужные поля ввода для нот и прочих параметров. Перед проигрыванием скрипт получает данные от трекера и компилирует их в описанный выше формат, в виде директив ассемблера, добавляет исходник движка, и собирает встроенным (написанным на том же AngelScript) кросс-ассемблером Z80. Собранный бинарный файл проигрывается встроенным эмулятором. Скрипт также реализует экспорт данных наружу: ассемблерный исходник с данными музыки или собранные файлы популярных форматов (AY, TAP, SCL).

Что касается управления движком со стороны интерфейса трекера, я решил сделать всё максимально просто и понятно. Вместо настроек трека и системы инструментов, каждый из параметров нот задаётся непосредственно в треке. Хотя это увеличивает количество полей на экране, это всё же проще для понимания и удобнее для работы: всё сразу под рукой.

QChan24 в 1tracker

Нотный текст делится на пять или шесть блоков-столбцов, то есть каналов. Первые четыре или пять — тональные каналы, последний — канал перкуссии. Также есть дополнительные столбцы для задания темпа и слайда.

В каналах тона можно ввести следующие параметры:

  • Нота.
  • Расстройка частоты ноты, от 1 до 9.
  • Признак легато, пусто или 1.
  • Громкость, от 1 до 16 (от тихой до громкой).
  • Скорость огибающей, от 1 до 8 (от быстрой до медленной).
  • Режим огибающей, от 1 до 3 (атака, спад, удержание).

Расстройка и легато применяются только на ту ноту, для которой они указаны, а настройки огибающей распространяются на все последующие ноты в канале. У первого канала, поддерживающего слайд, есть ещё два дополнительных поля: скорость слайда вверх и вниз, от 1 до 9.

В канале перкуссии вводятся следующие параметры:

  • Номер сэмпла, от 1 до 99.
  • Громкости, от 1 до 4.
  • Фильтр, от 1 до 9 (от глухого звука до нормального).
  • Смещение начала сэмпла, от 1 до 9.

▍ Заключение


К сожалению, на момент публикации статьи музыкальные шедевры с применением этой технологии пока ещё не созданы. Да и у меня не нашлось времени для создания полноценного демонстрационного трека, поэтому пока можно послушать очень короткий тест.

Скрипт движка пока не включён в релизную версию 1tracker, но его уже можно найти на тематическом форуме или в посте в моём личном блоге. Он будет включён в базовую поставку с ближайшим обновлением программы.

Работа же над новейшими достижениями в области древнейших технологий продолжается. Зачем? Потому что можем.

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (47)


  1. Zara6502
    18.09.2024 10:05
    +4

    Спасибо, ролик с утра посмотрел на ютубе. Не знал совсем о таком направлении в формировании звука. Звучит конечно необычно.

    Кстати, а не по тому ли принципу звук формировался на PC Speaker в 80-90-е? я слушал MOD файлы таким образом.


    1. shiru8bit Автор
      18.09.2024 10:05
      +2

      Для воспроизведения цифрового звука на PC Speaker на двух каналах таймера делался вполне честный ШИМ, с эквивалентным разрешением в 6 бит. Но в староглиняные времена и в играх на PC применялись подобные техники. С этим быстро завязали, так как новые процесоры с другими таймингами появлялись как грибы после дождя, а потом пошли звуковые карты, и смысл пропал.


      1. Zara6502
        18.09.2024 10:05

        с играми может и завязали, а вот музыку делали весьма долго.

        по играм есть новодел где такое применяется - Planet X3


        1. shiru8bit Автор
          18.09.2024 10:05

          Так совпало, что в Planet X3 звуковой код делал я, и я там такого не применял (хотя хотел, но Дэвид был против). Может потом добавили, там были всякие пользовательские модификации типа расширенных видеорежимов.


          1. Zara6502
            18.09.2024 10:05

            я смотрел только его ролики по созданию игры и там у него в одном из роликов были вставки с теми кто делал музыку, в одном из рассказов речь шла о том, что на спикере кажется реализовано 3 канала, могу за выходные найти кусочек и указать таймкод. Как мне помнится это было в релизной игре сразу.

            Я могу неправильно понимать сказанное в видео, но точно помню что на спикере проигрывался синтез его главной темы игры.


          1. Zara6502
            18.09.2024 10:05

            видео с канала Дэвида

            на 4:25 упоминается про PC Speaker и 3 виртуальных канала.

            на 5:58 примеры звучания


            1. shiru8bit Автор
              18.09.2024 10:05
              +2

              Для этой игры я делал звуковой код, редактор музыки и заглавный трек. Так что я примерно понимаю, о чём идёт речь. Про эту технику я давеча рассказывал на Хабре, там же упоминается и Planet X3.


  1. qiper
    18.09.2024 10:05
    +6

    Особенно 3 трек впечатлил


  1. registerconsumet
    18.09.2024 10:05

    Зачем? Потому что можем.

    но зачем


    1. Wesha
      18.09.2024 10:05
      +3

      Это сейчас юниоры меряются пиписьками. А мы мерялись тем, кто круче код напишет.


    1. shiru8bit Автор
      18.09.2024 10:05
      +5

      Скрытый текст


  1. vvbob
    18.09.2024 10:05
    +4

    Звучит довольно круто, как по мне даже получше чем 128 со звуковым чипом. Круто!


    1. voldemar_d
      18.09.2024 10:05
      +2

      У меня тоже иногда такие ощущения возникают. У чипа AY невозможно менять скважность прямоугольного импульса, только частоту. А в биперной музыке можно. Насколько я понимаю, в музыкальном чипе SID для Commodore64 скважность менять можно, и отличия в характере музыки для него сразу узнается по характерным звуковым эффектам - например, в самом начале музыкального трека игры Monty on the run слышно, как плавно скважность изменяется.


      1. shiru8bit Автор
        18.09.2024 10:05
        +2

        У SID не просто можно менять скважность, там безумные 4096 ступеней, можно менять её очень плавно.


        1. voldemar_d
          18.09.2024 10:05
          +1

          Да, вот тут это хорошо слышно (и видно, благодаря визуализации).

          Честно говоря, жаль, что в AY такой возможности нет. Добавление её, кмк, вряд ли бы сильно усложнило чип. А звучание это украшает и разнообразит весьма заметно. Интересно, известно ли, какой из двух чипов оказался в результате более распространен в мире? И для какого из них больше коммерческой музыки было создано, т.е. для официально продаваемых игр, например?


          1. shiru8bit Автор
            18.09.2024 10:05
            +1

            Действительно, очень жаль. Управление скважностью добавили в AY8930, восемь ступеней, звучит прекрасно. Кстати, из упущенных возможностей: в оригинальном даташите на AY-3-8910 упоминался некий второй бит громкости огибающей, но в железе его не было.

            Вроде бы больше музыки было для SID, около 55 тысяч композиций, тогда как у AY порядка 30 тысяч. Но это не точно, я давно не следил за ситуацией. С коммерческими играми ситуация сложнее: SID был только на C64, а AY на десятках компьютеров и в некоторых аркадах. Тут скорее преимущество на стороне AY.


            1. vladkorotnev
              18.09.2024 10:05
              +1

              в оригинальном даташите на AY-3-8910 упоминался некий второй бит громкости огибающей

              Это не тот случайно, который потом "вылез" в ямаховской копии чипа и стал предметом тучи холиваров?


              1. shiru8bit Автор
                18.09.2024 10:05

                Нет, это ещё один бит в регистре громкости. Там нижние 4 бита прямая установка громкости и 4-ый - включение аппаратной огибающей, и в том же регистре в даташите был ещё 5-ый бит. В YM2149 другая история, там 5-битные ЦАПы для более плавной работы огибающей (меньше ступенек при затухании и нарастании). Хотя может быть какая-то связь действительно есть.


  1. Refridgerator
    18.09.2024 10:05
    +4

    Крутая статья, как и всегда. AY так и не смог вытеснить биперную музыку из моего сердца, у неё своё неповторимое, математически единственное очарование.


  1. hssergey
    18.09.2024 10:05
    +1

    Насколько помню, если в качестве PC спикера использовать обычный динамик от радиоприемника, то его диффузор обладал инерционностью, сглаживал импульсы, и звучало на слух приятнее. Современные ноутбучные динамики или кварцевые пищалки воспроизводят звук точнее, но звучит из-за этого хуже, с высокочастотным "звоном"...


  1. vanxant
    18.09.2024 10:05
    +3

    Статья оч. крутая, автору респект!

    Но просто замечу, что это (был) не единственный метод формирования полифонии. Для пары-тройки каналов вполне можно практически точно так же давать почти нормальную синусоиду, если давать импульсы с нормальной 50% скважностью, а не вот это вот всё. Вот например 2-3 канальный звук из игрушки 1986 года: https://www.youtube.com/watch?v=Fg5msRZvv_8


    1. shiru8bit Автор
      18.09.2024 10:05
      +5

      Да, конечно, есть и другие способы. В указанной игре используется самая простая и популярная процедура Марка Александра из редактора Wham The Music Box. Там всего два канала тона, 50% меандр, без громкости (с небольшим разбалансом) и прочих эффектов. Работает максимально просто: чередуем выводы каждого канала в бипер с частотой выше слышимого диапазона. Но уже на двух каналах даже на реале слышен некоторый звон (несущая), а на трёх каналах это большая проблема. И такие движки очень чувствительны к стабильности таймингов и перерывах в синтезе для логики плеера. Поэтому на этом принципе более 3 каналов не делают. Я сам использовал его в упомянутых в статье движках Phaser (2 канала с модуляцией) и Tritone (3 канала с управлением скважностью и разбалансом громкостей).

      Про синусоиду в The Music Box, конечно, речи не идёт, но разработки последних лет показали, что и её аппроксимация возможна. И не только её, но даже и вполне настоящих LP/HP фильтров. Но это уже полное математически-кодовое безумие, и на пальцах в посте его не разобрать. Зато так может звучать всё тот же бипер на Z80 и 3.5 МГц.


      1. voldemar_d
        18.09.2024 10:05
        +1

        Помню, еще на реальном Спектруме как-то достал этот Wham, потыкался в него буквально полчаса и забросил. Как-то не произвело впечатление то, что на нем можно реализовать. Не помню, в какой известной игре была на нем музыка сделана, но даже близко ничего похожего по звучанию сделать не получилось.

        Помню, была какая-то версия Tetris (не Tetris 2) с похожей 2-голосой биперной музыкой, но она и то приятнее звучала.


        1. shiru8bit Автор
          18.09.2024 10:05

          Wham был моим первым знакомством с компьютерной музыкой, первым музыкальным редактором, который я увидел. Но я тогда не особо интересовался музыкой в плане сочинения, так, рандомно потыкал в ноты и забросил. Интерес пришёл позже, с Sound Tracker для AY, и это был уже совсем другой разговор.


      1. voldemar_d
        18.09.2024 10:05

        И не только её, но даже и вполне настоящих LP/HP фильтров. Но это уже полное математически-кодовое безумие, и на пальцах в посте его не разобрать. Зато так может звучать всё тот же бипер

        Если честно, не очень понятно, где в этом звучании вполне настоящий LP/HP-фильтр. Скорее, синтез звуков, в части которых высокочастотных гармоник больше, а в части меньше. Это с натяжкой можно назвать LP-фильтром, а HP-фильтра здесь вообще нет. HP-фильтр обрезает низкие частоты, а в этой демке они все время присутствуют. Возможно, надо знать, что под капотом у этого движка, чтобы стало понятнее, о каких фильтрах речь.

        Кстати, по поводу низких частот на бипере. На эмуляторах да в наушниках с выхода звуковой карты низкие частоты слышны хорошо, но на реальном Спектруме с бипером-пищалкой низких частот почти нет. Возможно, поэтому в 90-е у меня биперная музыка вызывала некоторое отторжение. Помню, когда запустил первый раз игру Agent X, музыка на заставке вызвала реакцию "это что вообще такое было?", а вовсе не восторг.


        1. qiper
          18.09.2024 10:05

          На эмуляторах да в наушниках с выхода звуковой карты низкие частоты слышны хорошо, но на реальном Спектруме с бипером-пищалкой низких частот почти нет. Возможно, поэтому в 90-е у меня биперная музыка вызывала некоторое отторжение.

          А как же динамик телевизора?


          1. shiru8bit Автор
            18.09.2024 10:05
            +1

            У оригинального Спектрума такой прикол, нет выхода звука наружу. Только встроенный маленький, очень тихий динамик. Для него даже продавались специальные прибамбасы, типа колонки с усилителем. А у некоторых отечественных клонов другой прикол, пьезоэлемент вместо динамика - на таких музыка в Agent X звучит как шум.


            1. voldemar_d
              18.09.2024 10:05

              У меня был вроде нормальный динамик, не пьезо, но музыка в Agent X звучала тоже так себе.


          1. voldemar_d
            18.09.2024 10:05
            +1

            Хороший вопрос. У меня оригинального Спектрума не было, только пара клонов. Пытаюсь вспомнить, куда в них выводился звук бипера - вроде бы, на встроенный динамик, но не на телевизор - в нем был только самопальный вход RGB.

            Вот выход AY был точно выведен на внешний усилитель, в качестве которого использовался виниловый проигрыватель Вега-104, и звук в колонках был просто отпад (я не преувеличиваю), с очень сочными, но аккуратными низами. Демки вроде Song in lines я мог часами по кругу слушать.


  1. vladkorotnev
    18.09.2024 10:05
    +3

    О, у вас попонятнее вышло описание принципа действия, однако. Не так давно тоже писал статью про биперщину, не на спектруме, правда — надо будет ссылочку поставить на здешнее описание :-)


    1. shiru8bit Автор
      18.09.2024 10:05

      Очень интересный материал, спасибо! Мне в рекомендациях Хабра почему-то до сих пор не выпал, хотя казалось бы.


  1. engine9
    18.09.2024 10:05
    +2

    Спасибо, чувствуется что с любовью писалось.


  1. Pyhesty
    18.09.2024 10:05
    +1

    Апогею в этом отношении повезло =) у него был ТРЕХ! канальный биппер ) не так давно с удовольствием пробовал подобрать под него музыку (без музыкального слуха тяжелова-то, но более менее звучит =)

    н о вообще разрабатывать музыку под бипперы - это адский труд имхо =) тут нужно иметь безграничную любовь к музыке

    Апогей =) звук на ВИ53


    1. shiru8bit Автор
      18.09.2024 10:05

      У меня есть реальный Апогей и план как-нибудь сделать под него, а также Вектор и Партнёра универсальный трекер, который будет собираться из одного исходника сразу под все эти платформы. Но пока всё никак не доходят руки.


      1. Pyhesty
        18.09.2024 10:05
        +2

        супер! напишите, как руки дойдут! =)


  1. abkomarov
    18.09.2024 10:05
    +2

    Спасибо, не часто такие статьи попадаются, это как теорема Ферма ) толку от неё ноль, только одно сплошное удовольствие )


  1. voldemar_d
    18.09.2024 10:05
    +2

    Интересно, как делают визуализацию биперной музыки в роликах на ютубе? Визуализация музыки для AY хотя бы понятно, на чем основана: сгенерировать прямоугольные импульсы, сложить их и нарисовать результат с каким-то масштабом по горизонтали - тут хотя бы более-менее понятно, как это сделать. А как можно визуально "сэмулировать" колебания звука с бипера? Как в ролике с музыкой Electric B12 в этой статье, например.

    Ещё мне таки не очень понятно, каким образом в биперных движках реализовано изменение громкости? Просто изменением длительности коротких импульсов - чем он дольше, тем громкость выше? Но ведь это влияет не только на громкость, но и на частотную окраску звука, насколько я понимаю - скважность при этом тоже изменяется, так ведь?

    Скрипт также реализует экспорт данных наружу: ассемблерный исходник с данными музыки или собранные файлы популярных форматов (AY, TAP, SCL).

    Мне вот интересно, а как биперная музыка конвертируется в формат AY - это же формат для воспроизведения на чипе AY? В нем же музыка играется фрагментами по 20 мс, но тогда получается, что какой угодно биперный звук так сыграть нельзя?

    И ещё вопрос не про биперы. Музыка для чипа AY основана на том, что значения регистров изменяются в прерываниях с частотой около 50 fps, т.к. обычно она играется из процедуры обработки прерываний IM2. Есть ли на Спектруме какие-нибудь демки или музыка на заставках игр, в которой значения регистров изменяются с более высокой частотой? Я помню, что есть некая демка от Max Iwamoto, в которой на чипе AY играется отсэмплированная композиция Metallica, но такое было интересно в 90-х годах, и тогда это звучало круто, но сейчас качество такой музыки, прямо скажем, не очень-то восхищает.

    Есть ли примеры "промежуточных" вариантов, когда, например, какие-нибудь арпеджио или другие музыкальные эффекты играются с более быстрым изменением нот, чем 50 нот в секунду - 100 или 200 в секунду? Как я понимаю, для реализации такого нужно применять изменение регистров AY не из прерываний, а выдерживая паузы с помощью команд самого Z80, примерно как в биперных движках.


    1. qiper
      18.09.2024 10:05
      +1

      Но ведь это влияет не только на громкость, но и на частотную окраску звука, насколько я понимаю - скважность при этом тоже изменяется, так ведь?

      Да, при изменении скважности меняется и громкость и тембр


      1. voldemar_d
        18.09.2024 10:05
        +1

        Возможно, на это можно забить, но не мешает ли это, скажем так, реализовывать свои музыкальные задумки именно так, как хотелось?


        1. qiper
          18.09.2024 10:05
          +1

          Не мешает


    1. shiru8bit Автор
      18.09.2024 10:05

      Про визуализацию я не знаю, пока не занимался этой темой.

      Да, изменение ширины импульса меняет окраску звука, тембр, так как возникает иной набор гармоник. Это заметно на слух, чем выше эта «громкость», тем звук жирнее. Но это неизбежный компромисс, чтобы получить хоть какой-то контроль громкости, и придаёт определённый характер звучанию.

      Формат AY не обязательно работает пакетами по 1/50 секунды, там можно просто запустить зацикленный код, и он будет корректно играть. Поэтому собственно и возможно помещать туда биперную и цифровую музыку.

      Да, повышенная частота обновлений регистов AY практиковалась на других платформах, в особенности широко на Atari ST, где были придуманы различные модуляционные эффекты, подобные биперным, но для каналов AY. На ZX были демки, играющие такую музыку, хотя насчёт частоты основного обновления не уверен. Также была экспериментальная версия AY-трекера под 100 или 200 Гц, точно не помню ни параметры, ни названия. В голове крутится что-то типа PSM.


      1. voldemar_d
        18.09.2024 10:05
        +1

        Эту демку я видел, интересно бы узнать хоть какие-то подробности про то, как это всё реализовано. Особенно про это:

        Can't be used in Russian clones because of processor and ULA timings

        Только на фирменном Спектруме с правильными таймингами заработает? А на каком-нибудь Spectrum Next? Эту демку можно где-нибудь взять в исполняемом виде (TAP, TRD)?

        Интересно, как можно на AY сэмулировать переменную скважность. Её здесь действительно слышно - например, в сольной партии примерно с 1:00. Просто с большой точностью менять значение делителя частоты тона в определенные моменты времени, тщательно рассчитывая задержки исполнением циклов на Z80?


        1. shiru8bit Автор
          18.09.2024 10:05
          +1

          Я не вникал в код, но думаю, там просто включают-выключают маску тона в микшере AY, кодом, примерно похожим на биперный синтез. Проблемы с таймингами в том, что частота AY и частота процессора отличаются, просто будет нестрой между программными и аппаратными каналами. Но никаких принциальных препятствий для создания версии для отечественных машин нет.

          Скорее всего канал с изменяемой скважностью просто полностью программный, мне так кажется. Это проще, чем синхронизироваться со счётчиком в AY, ведь неизвестно, в какой фазе он вообще находится.

          Я помню, что смотрел демку в Unreal Speccy, но не помню, где брал. Сейчас найти её не представляется возможным из-за названия, было много чего с таким наименованием. Даже это видео еле нашёл.


          1. voldemar_d
            18.09.2024 10:05

            Нашел вот такое обсуждение SID-плееров для Spectrum, но если я правильно понял, там это делается на двух чипах AY (turbo sound) и Z80, разогнанном до 14 МГц. Но там куча информации со всякими подробностями, в которых я мало что понимаю. Там же упоминается некий архив SID-музыки, в котором, и правда, больше 55 тысяч композиций, если я правильно понял.


      1. voldemar_d
        18.09.2024 10:05
        +1

        Кстати о визуализации. В каментах к видео можно почитать, как человек заморочился, чтобы такое создать. Дошло даже до того, что он расковырял код плеера и научился заглушать отдельные голоса в многоголосом движке.

        А вот тут упоминается некая программа corrscope.


  1. voldemar_d
    18.09.2024 10:05
    +1

    Забыл спросить про самомодифицирующийся код. Понятно, что это делается для максимальной производительности. А он на ходу прямо во время воспроизведения генерируется? Или в самом начале перед воспроизведением вся композиция целиком разворачивается в конечный код, который потом дальше уже исполняется и больше не изменяется? Если второе, сильно ли код увеличивается в объеме?


    1. shiru8bit Автор
      18.09.2024 10:05
      +1

      Код как правило модифицируется в парсере строк нотного текста. То есть, парсим строку, получаем параметры и модифицируем код в цикле синтеза, чтобы он синтезировал нужный звук (устанавливаем операнды в опкодах, или подменяем некоторые опкоды). Далее генерируем отрывок звука длительностью в строку, и потом на следующей строке код снова модифицируется. Внутри звукового цикла самомодификация применяется редко, просто потому что она не сильно-то быстрая.

      Например, в движке wtbeep есть 32 разных тембра, которые генерируются 5-байтовыми фрагментами кода. В начале каждой строки движок смотрит, в каком канале какой тембр выбран, и собирает код из этих фрагментов, копируя их из заготовок в нужные места цикла синтеза.