В онлайн-эмуляторе aa-dav.github.io/ доступен новый исходник «bitmap noise» — выбираем пункт и нажимаем «Compile and Run».
После выполнения инициализирующего кода в simple_lib.inc раскладка памяти следующая:
С адреса $8000 располагаются 16384 _слов_ (ячейки памяти 16-битные) описывающие 16-цветный битмап 256x256 (на экране без записи в регистры скроллинга видны верхние 192 строки). Т.к. ячейки — слова, то в каждое слово влезает четыре пикселя по 4 бита цветности на каждый. Раскладка линейная.
Но еще с адреса $C000 располагается зона атрибутов размером 32x32 знакоместа. Атрибут имеет маску PPPP00NN NNNNNNNN, где N это номер выводимого знакоместа в соответствующей квадратной сетке, а PPPP — верхние биты палитры из 256 цветов которые будут добавляться к цветам пикселя. Т.е. номер субпалитры в 16 слотов из 16 субпалитр. Чтобы записать в палитру надо записать в ячейку/порт $FFF6 номер слота палитры (0..255), а в $FFF7 RGB-значение в формате 0RRRRRGGGGGBBBBB.
Таким образом на экране может быть одновременно 256 цветов, но не более чем 16 цветов в знакоместе.
Более того — отображение в знакоместах можно перенастраивать на другие знакоместа через таблицу атрибутов реализуя быстро работающие текстовые видеорежимы ну или игровое тайловое поле с аппаратным скроллингом.
Размышляя на досуге о том не присобачить ли какой-нибудь ЯВУ вместо ассемблера просто подумал — а насколько архитектура Simp4 хорошо ложится на крайне минималистичный язык Forth?
А ведь есть весьма хороший из-за симметричности системы команд уровень «природной» совместимости.
Во первых т.к. в Simp4 нет байтов, а и регистры и ячейки памяти есть 16-битные, то Forth уже удачно ложится по своей безтиповой стековой модели. Угу.
Во вторых если рассмотреть базовые арифметические операции Forth — они производятся над двумя величинами в вершине стека которые замещаются одним результатом.
Но в Simp4 этому крайне удачно соответствует сам принцип работы со стеком и в точности, например, операция форта + будет реализована всего одной машинной командой размером в минимальное одно слово:

[ sp ] = [ sp ] + [ sp ] ; считываем со стека два операнда (с пост-декрементом), суммируем и отправляем обратно в стек (с пост-инкрементом)


Вместо + может быть и минус и вся прочая базовая арифметика процессора.
Такая адресация используя специфичное поведение регистра sp при косвенной адресации реализует именно что базовую модель форта — со стека снимаются два аргумента, помещаются на вход АЛУ (sp каждый раз при «изъятии» пост-декрементируется) и далее результат из АЛУ записывается обратно в стек с пред-инкрементом. Ровно то, что нужно форту.

Сложнее, например, с командой DUP. Она должна продублировать вершину стека. Но записать это как

[ sp ] <- [ sp ]; аналог move с нулём в качестве второго операнда режима сложения с inplace-immediate


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

r0 <- sp + 0
[ sp ] <- [ r0 ]


Т.е. загоняем в r0 копию указателя на вершину стека и вот её уже дублируем.
Но интересно тут то, что константа 0 в используемом тут режиме inplace-immediate (может полностью отсутствовать с кодом операции + как во второй инструкции) может быть любым числом от -8..+7, что позволяет уложить этот режим в одно слово инструкции. А значит во первых — ряд инструкций которым нужно дотянуться неглубоко в стек (до -8 от вершины) могут использовать этот сокращённый режим инструкции, а форту больше и не надо.

Однако такое удобство стека в Simp4 вроде бы как ставит подножку — использовать его уже понятно, что очень удобно для форта, но как же переходы в другие участки кода (CALL-ы) которые тоже используют стек?
А вот как: в Simp4 на самом деле по архитектуре нет команды CALL, а она эмулируется последовательностью из, например, таких инструкций:

[ sp ] <- pc + 2
pc <- proc_address


Т.е. да, в Simp4 мы всегда сами помещаем в стек «вручную» адрес возврата предварительно его вычислив где опять таки пригождается сложение в режиме inplace-immediate, т.к. желательно уложится в одно слово и потом мы совершаем безусловный прыжок куда надо. Такая последовательность инструкций занимает уже три слова, т.к. proc_address как полное 16-битное слово надо размещать за последней инструкцией. Поэтому в стек в первой команде помещается pc+2 — на момент выполнения этой инструкции pc уже смотрит в начало следующей которая занимает 2 слова, вот мы и вычисляем адрес возврата сразу за ними.
Окей, но это для нас значит, что мы можем вообще изменить стратегию вызова процедур в режиме форта и не использовать для неё регистр sp!
Можно применить совмещённую стратегию программного стека и LINK REGISTER. Договоримся, что регистр r4 указывает на слово сразу под вершиной программного стека для вызова процедур и тогда вызов будем писать так:

[ r4 ] <- pc + 2
pc <- proc_address


Это всё те же три слова инструкций, но регистр r4 сам не декрементируется. У него в отличие от sp такой фичи нет.
Поэтому если процедура не вызывает другие процедуры (leaf-node) она просто не портит r4 и делает возврат тривиально:

pc <- [ r4 ]


И ячейка на которую указывает [ r4 ] снова готова к использованию без какого либо пенальти по сравнению с классическим стилем вызова процедур в Simp4 как я его использую.
Но если процедура собирается вызывать другие процедуры, то она должна начинаться с тривиального пролога:

r4 <- r4 - 1


Просто при входе декрементируем r4 в одну инструкцию-слово.
И возврат из такой процедуры усложняется всего лишь на одно слово:

r4 <- r4 + 1 ; увеличиваем r4 на 1
pc <- [ r4 ] ; переходим по адресу возврата в ячейке куда указывает r4


И вуаля! Такой отказ от использования аппаратного стека для подпрограмм совсем не выглядит катастрофой, обременительные расходы совсем небольшие, а для leaf-node-процедур вообще нулевые. Зато гибкость аппаратного стека для арифметики самого Форта выглядит солидным достижением.

Забавно, забавно. Simp4 оказывается сравнительно дружелюбная архитектура к Форту хотя таковой совсем не планировалась. :)
  • avatar MixailV
  • 0
Спасибо за такой подробный и развернутый ответ!
Кодить что-то своё наврядли буду, просто было интересно )
Про PDM даже мысли в голову не приходили. По идее следующий шаг после PWM — это реализовать дельта-сигма модуляцию!
Забыл ответить точнее на твои варианты. Движков через AND или XOR я не знаю (AND звучит как неважная идея, XOR может и заработать, не пробовал). Идея с чередованием — предпоследняя в моём списке, так делают многие старые движки, да и новые тоже. Недостаток этой идеи: трудно сделать много каналов, т.к. часто делаем out, причём такая схема требует высокой частоты дискретизации (из-за частого переключения порта, мы производим больше высокочастотных помех). Из-за этого, например, движок Wham так сильно свистит.

Squeeker смешивает звуки по OR, что может звучать странно, но работает неплохо. Мой Octode XL как бы складывает каналы вместе и использует сумму как итоговую скважность (фактически, PWM).

Было бы интересно сделать биперный движок на PDM. К сожалению, не знаю как синтезировать PDM с нужной скоростью.
Есть много подходов. Вот прямо из головы несколько:
* Можно выводить звук каждого канала тонкими иголками (тонкими = намного уже периода цикла дискретизации). Тогда громкость можно изобразить меняя ширину иголки. Так были устроены движки Фоллина, и это довольно популярный подход.
* Так как иголки сравнительно узкие, а периоды нот сравнительно длинные, большую часть времени ничего выводить не нужно. Поэтому можно сделать иголки пошире, например шириной в период дискретизации, или даже в несколько. Это идея довольно редкая, но так устроен, например, Octode XL.
* Можно думать об этом как о чём-то вроде ШИМ. Тогда можно просто сделать движок как что-то типа ШИМ движка, и кормить ему сэмплы, как в обычном цифровом движке. utz делал несколько современных движков по такому принципу, и наверное не он один (но я не вспомню сейчас их названия).
* Можно выводить текущее состояние каждого канала звука раз в цикл дискретизации. Типа, 2 канала выдают прямоугольники, каждый со своим периодом, тогда выводим в звуковом цикле поочерёдно состояние каждого из каналов. Так работают Wham the Music Box, Savage, из новых движков Tritone, ну и многие др., это чуть ли не самая «модная» стратегия.
* Можно смешивать подход с иголками и подход с прямоугольниками. Это тоже довольно редкая идея, но Squeeker устроен как раз так. Плюс такой идеи в том, что прямоугольники лучше воспроизводят басы, а иголки дают более разнообразное звучание.
Вроде ничего фундаментального я не забыл, но пишу второпях. Надеюсь, что Shiru дополнит/поправит, если что.
  • avatar MixailV
  • 0
Никогда не вникал в эту тему, но стало интересно. А как смешивают каналы? Складывают через and, ксорят, или чередуют, выделяя некий квант времени на проигрывание каждого?
просто интересно, чтобы ты сказал о других классических биперах, как-то: cybernoid-2, last ninja, robin of the wood, sooty and sweep, hate, renegade, soldier of fortune, flying shark… для необразованного меня в те года все движки делились на два типа: «вхам» и «не-вхам» (и этот второй еще на две разновидности: «тихие» и «громкие»)))
Вот список треке в старом сборнике, кот я делал в 1994: SAVAGE 1, SAVAGE 2, SAVAGE 3, MiG-29, GOLDEN AXE, CHRONOS, RAMPARTS, SOLDIER OF FORTUNE, FAIRLIGHT, SABOTEUR 2, FOX FIGHTS BACK, GRYZOR, RACE AGAINST TIME.

В 2013 я переделал этот сборник, существенно расширив его. Но Cybernoid 2 у меня всё равно не попал. И даже если бы я брал что-то на этом движке, я бы 100% взял титульный трек из Stormlord.
  • avatar aa-dav
  • 0
Но в остальном, скажу честно, у меня там наверное больше несовпадений со списком Shiru, чем совпадений.

Cybernoid 2 присутствует? :)
  • avatar Shiru
  • 0
Мимо он не прошёл, был на кассете с Ping-Poing и Thanatos. И я действительно вспоминал обоих Saboteur'ов, когда делал список, но всё же не включил. Но да, там тоже очень интересное звучание и сама музыка.
Поскольку я очень любил бипер, а слушать бипер на реале можно только загружая софт, что медленно даже с дисководом, я делал себе сборник своей самой любимой выдранной биперной музыки. Возможно тебе будет приятно узнать, что Saboteur 2 у меня там был выдран. Но в остальном, скажу честно, у меня там наверное больше несовпадений со списком Shiru, чем совпадений. Просто не выберешь TOP 10 или ТОП 20 чтобы угодить всем.
удивлён, что не запомнился и не впечатлил Saboteur-2
мимо-то он точно пройти не мог
  • avatar Shiru
  • 3
Да, определённо раньше. В 1994-1995 я только осваивал Бейсик, и никакого системного софта у меня просто не было. В 1996 стал использовать MCODER2, стал писать первые процедурки в машкоде, потом добыл ZEUS, Gens. А полностью на ассемблере начал писать программы в 1997.

Я не то, чтобы извиняюсь, но иронизирую над проблемой восприятия. Это касается не только бипера, но любого чиптюна. Если вдруг меня спрашивают про занятия музыкой, мне очень трудно объяснить, что и почему я делаю, и почему оно звучит так необычно, не как из радио. Многие определяют этот диапазон звучания как 'что-то игрушечное', кому-то трудно воспринять это как музыку в принципе. С другой стороны, я в принципе не отношусь к чему-либо излишне серьёзно (музыка-игры-демки-разработка), несмотря на то, что это типа как мои основные занятия в жизни.

Согласен, главное качество звуковых чипов — унификация звучания, по крайней мере значительное снижение диапазона возможностей по звучанию, в обмен на облегчение жизни процессору. И у большинства чипов есть некоторые скрытые фишки, которые со временем были найдены, изучены, и существенно расширили потенциал. Огибающая у AY, коротко-периодический шум у SN76489, DPCM сэмплы тональных инструментов на NES, и так далее.

Также разделяю мысли, что бипер наиболее идеологически натуральное звуковое устройство для Спектрума, и наиболее интересное со стороны творческой свободы.
Ничего себе, получается, что я закодил свой первый биперный движок раньше тебя, году в 1994 или в 1995.

Выбор любимых треков вещь очень индивидуальная, трудно обсуждать её в деталях. Но мне было очень интересно узнать, что ты пришёл на спектрум через денди и это в какой-то степени определило твои вкусы и приоритеты. Ты как бы мерял всё что видел в терминах денди и поэтому, даже в этом посте, неоднократно за бипер извиняешься (чисто для примера, «биперная музыка звучала крайне примитивно даже для не самого искушённого уха»). Может быть в силу того, что я довольно долго просидел на 48К машине (примерно года 4 или 5), я впитал бипер совершенно органически, до такой степени, что почти всю подряд АУ музыку 1990х я агрессивно не любил (а АУ музыку 1980х просто воспринимал как «блямканье»). Отчасти это можно попробовать объяснить музыкой, которую я слушал вне спектрума — в то время я слушал много инди-рока, и звук АУ был просто агрессивно в этом смысле неправильным, раздражающе «игрушечным» (в то время как фэйд-ауты на скважности, типичные для бипера, имеют примерно те же самые тембры что перегруженая гитара, так что «роковый» звук треков Фоллина не случайное совпадение).

Но мне кажется, тут есть ещё одно измерение. Я бы сравнил АУ с чем-то типа графического сопроцессора у популярных консолей 1980х. Они, конечно, помогают центральному процессору с отрисовкой графики и расширяют диапазон того, что можно вообще показать. Но ещё они предоставляют достаточно ограниченный набор инструментов и, в итоге, впихивают то, что все делают, в достаточно узкие рамки, сужая в итоге разнообразность визуальных или звуковых эффектов. Демосцене в каком-то смысле повезло с АУ, т.к. демосцена придумала радикально новый способ использования АУ, благодаря реинтерпретации смысла огибающей. Фактически, демосцена открыла в АУ новый канал.

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

Вот эта свобода и есть то, что заставляет меня возвращаться к биперу снова и снова.
«В настоящий момент, товарищ Поликарпов, мы не можем предоставить вам других писателей»
aka
«Других писателей у меня для вас нет»
  • avatar sq
  • 0
А чё у вас на всех последних фотках одни и те же люди-то!
  • avatar Vinnny
  • 1
  • avatar Eugene
  • 1
Всё верно, состояние состоит из позиции и значения регистра D. То есть для каждой позиции вычисляется 8 результатов.
Ммм. У меня в голове это укладывается следующим образом: текущая длина ссылки является «состоянием». Поэтому если ты вставляешь команду изменения длины ссылки, ты бы должен по идее как-то сохранять смену состояния декомпрессора. Типа, при парсинге вперёд, ты бы должен по идее фиксировать, для каждой позиции в данных, не просто текущий оптимальный результат сжатия до этой позиции, а 8 результатов сжатия — по одному для каждого текущего состояния длины ссылки. Понятно что если такой контекст и влияет на результат, то довольно слабо, но хочется понять, как ты избегаешь учёта контекста совсем.
  • avatar Eugene
  • 0
как именно там выбираются моменты для вставки кодов расширения ссылок в Hrust 1
Это просто: на каждом шаге компрессор пробует вставить от нуля до семи команд расширения.

укорачивать коды вроде тоже декомпрессор позволяет.
Действительно, благодаря тому, что в декомпрессоре используется циклический сдвиг (RRC D), регистр D можно сбросить до начального состояния. oh1c это учитывает. Это улучшает сжатие в среднем на 0.02% ;)