Навигация по сайту
- Игры / Образы
- Игры на русском языке
- Коды / Советы / Секреты
- Наши переводы
- Наши проекты
- Игры на русском языке (OnLine)
- Эмуляторы
- Обзоры игр
- Информация
- Статьи
- Интервью
- Мануалы / Инструкции
Случайная игра
Вступай!!!
Облако тегов
Показать все теги
Как есть устроен эмулятор
Преамбула
Согласно умным книжкам, эмуляция – это поведение одной компьютерной системы (хост), имитирующее поведение другой компьютерной системы (гость). Результатом является возможность выполнения программ одной компьютерной системы под управлением другой (или использование периферийных устройств, предназначенных для гостя) вопреки различиям между ними.
Поведение компьютера, которое мы имитируем, – это обработка информации. Компьютеры отличаются тем, какие методы они предоставляют нам для работы с информацией, что делает их пригодными для определенных целей. Однако, цели, с которыми их можно использовать, сильно ограничены, и изначально многие из них просто не способны делать то, что могут другие. Но сердцу не прикажешь, человеку сильно хочется делать больше, чем позволяет его компьютер. И самым прямолинейным решением является собственно приобретение компьютера, способного делать что-то большее. На этом построена вся индустрия компьютерного железа (аппаратного обеспечения), там люди наживаются на наших желаниях расширять свои возможности. Но не все такие богачи, чтобы все это скупать по первому зову левой пятки, а кое-кто просто жадный, и вот – человечество изобрело способ заставить один компьютер вести себя как другой.
Само слово «эмуляция» было придумано в IBM, когда специалисты искали способ сделать новые компьютеры обратно совместимыми с предыдущими моделями, то есть способными выполнять программы для них. На тот момент уже существовал метод симуляции поведения одного девайса другим, и это была чисто программная модель, копирующая все особенности гостя виртуально.
Ребята придумали использовать для этих целей микрокод на уровне железа вместо программы, и это существенно увеличило скорость работы виртуальной модели. Именно этот метод они и назвали «эмуляция». По неизвестным причинам, в таком значении это слово давно перестало использоваться. Раньше любая имитация чего-либо программно называлась симуляцией, а методами микрокода – эмуляцией. Сейчас симуляция – это программное моделирование явлений, о которых невозможно иметь исчерпывающую информацию (например, природные явления), а эмуляция – это имитация поведения электронного устройства, логика которого может быть подробно изучена.
Что же представляет собой это поведение? Как именно компьютер обрабатывает информацию? Да и что вообще такое эта информация?
В физической реальности информация не существует. Она нематериальна. Однако, будучи сведениями, отражающими реальность, информация это не только то, чем могут обмениваться материальные объекты, но так же и принципы устройства и функционирования этих объектов, законы природы.
Информация может усваиваться и передаваться только в закодированном виде, именно тогда она приобретает физическую форму и превращается в данные. Но данные в чистом виде ничего нам не дадут: нам надо понимать, что они описывают, уметь их расшифровывать. И если данные, используемые природой, мы можем расшифровывать лишь ценой больших усилий, и то не полностью, нечто, закодированное самим человеком, может быть легко распознано им, если известны способ шифрования и смысл, который эти данные несут.«Информация есть запомненный выбор одного варианта из нескольких возможных и равноправных», – говорит синергетика. Минимальная единица количества информации определяется минимальным числом вариантов выбора – их может быть минимум два; и то, какой выбор из двух вариантов следует сделать, может быть описано одним битом. С битами работает двоичная система счисления, и (что отрадно) в биты может быть перекодирована любая информация, любое число. Компьютеру остается лишь совершить с этими битами нужные действия и выдать соответствующий результат.
Эмулирующее
Фундаментально эмулятор делится на две части, которые некоторые при разработке таки смешивают в кучу, но профи давно научились разделять: ядро и клиент.
Ядро – это собственно эмулятор консоли, полная имитация всех ее действий, нужных игрокам. Ядра отличаются точностью эмуляции, открытостью исходного кода, портируемостью. Точность выясняется на основе проходимости эмулятором специальных тестовых ROM'ов, разработанных с целью проверки реализации в эмуляторе особых приемов, которые позволяла консоль и которые использовались в играх. Открытость исходников зависит от лицензии, под которой написано и распространяется ядро. Существуют, например, такие варианты: платная; бесплатная с закрытым кодом; бесплатная с открытым кодом, не разрешенным к изменению; бесплатная с открытым кодом, разрешающая изменения в определенных пределах; бесплатная с открытым кодом, разрешающая любые изменения и использование кода в любых других проектах. Портируемость определяется возможностью скомпилировать ядро в обычный DLL файл и использовать в таких мультиплатформенных эмуляторах,как RetroArch или BizHawk, которые являются клиентами. Mednafen, напротив, распространяется со встроенными ядрами, хотя они и могут при желании быть скомпилированы в DLL и использованы вне его. Ну и есть эмуляторы, в которых ядро и клиент по хардкоду (не путать с хардкором) слиты воедино, и использовать их в таких мультиядерных клиентах не представляется возможным без существенных модификаций (FCEUX, DeSmuMe и большинство остальных).
Ядро генерирует видео, звук, другие виды вывода, если консоль это позволяет (дрожание геймпада, светомузыка, песни и пляски с цыганами и медведями), для генерации их она использует пользовательский ввод. Чтобы пользователь отправил ядру свой ввод, а на выходе получил требуемые развлечения, ядру нужна прослойка, которая бы сообщала его с компьютером. Этой прослойкой является клиент. Клиенты различаются целевой платформой, драйверами, пользовательским интерфейсом. Целевая платформа – это операционная система, под которую написан клиент, например Windows, Linux, MacOS, Android. Драйвера – это специальные средства работы с графикой, звуком, вводом/выводом, которые также заточены под определенную операционную систему (хотя могут быть и кроссплатформенные), и, собственно, обеспечивают нашу связь с ядром. Пользовательский интерфейс, в первую очередь, делится на интерфейс командной строки и графический, а уж в каждом из этих видов клепают кто во что горазд.
Заглянем, наконец, в ядро!
Оно должно работать с такой скоростью, чтобы визуально было неотличимо от работы консоли. Это простое требование таит в себе много головной боли для авторов эмуляторов.
Самое, пожалуй, важное понятие, касающееся этой скорости – кадр. Как и в кинематографе, изображение, показываемое на экране, меняется (обновляется) с определенной частотой.
Эта частота для подавляющего большинства старых консолей настраивалась равной частоте обновления экрана телевизора: для Америки и Японии 60 кадров в секунду (NTSC), для Европы и части Азии – 50 (PAL и SECAM).
Кадр картинки, выдаваемый консолью около полусотни раз в секунду, является основным плодом ее усилий и напоминает нам, что же такого революционного в таком жанре развлечения, как видеоигра. Все начиналось с летающих квадратов, но с развитием технологий человек получил возможность сначала управлять мультфильмом (когда реалистичность графики стала соответствующей), а потом и фильмом (ну, почти: тут еще не дотянули, но уже на грани).
Теперь к рутине. Довольно часто производители консолей стремились использовать топовые для своего времени процессоры, чтобы умыть конкурентов и дать по-настоящему уникальный экспириенс игроку, хотя, конечно, со скидкой на гарантированную возможность их серийного штампования («Дримкасту» не повезло с этим, и он стал фатальной консолью для «Сеги») и на юзабельность для игроделов (Atari Jaguar провалилась как раз из-за невозможности использовать пресловутые 64 бита без лютых танцев с бубном: Игорь в итоге потонул).
Тот факт, что процессоры были не абы какие, означает, что воспроизвести их работу программными методами сможет далеко не всякий комп. И ядра, всерьез заточенные на точность (соответствия изначальным параметрам консоли, соблюдения даже мелких нюансов), могут и на современных компьютерах тормозить, если эмулируемая консоль не достаточно далека от нас по времени. Так, качественная эмуляция PSX или Nintendo DS на старых машинах 60 кадров в секунду уже не выдаст, а эмуляция N64 может загрузить и аппарат посовременнее. И синхронная эмуляция нескольких процессоров – это только полбеды: есть еще трехмерная графика, которую современный юзер любит подвергнуть улучшениям и заставить эмулятор рендерить картинку не 320 на 240 пикселей, как делало оригинальное железо (отсюда такая пикселявость раннего 3D), а на весь экран, да еще и с дополнительным хитрым сглаживанием.
Так или иначе, частоту эмулируемой системы придется подгонять под частоту работы компьютера. Для этого ядру требуется какое-то понятие о времени. А так как процессор работает на фиксированной (и известной) частоте, то от этой частоты нам и надо отталкиваться как от фундаментальной. Так и делают сами процессоры: каждая инструкция в них выполняется определенное количество тактов. Процессорный такт – это минимальный юнит его работы. Длительность его определяется частотой, на которую настроен тактовый генератор процессора, именно он служит точным и надежным таймером для всей системы.
Генератор этот выдает волну, и все механизмы процессора синхронизируют по ней свою работу. Частота обычно выставляется равной одной операции, совершаемой процессором. Вот каковы эти операции: фетч (захавывание) инструкции, декодирование инструкции, выполнение ее (основа и цель всего происходящего, собственно вычисления), доступ к памяти и предоставление результата операции. В таком случае, можно говорить о средней частоте циклов за операцию. Если же частота выставляется меньшей, чем скорость выполнения одной операции, используют выражение "частота операций за цикл".
Центральный процессор
Основной цикл действий, выполняемых процессором, мы уже видели: прочитать память по адресу, указанному в счетчике инструкций (program counter), расшифровать содержимое в пригодную для выполнения машиной форму, произвести нужные вычисления и записать результат в регистры и/или память.
Вот что представляют собой считываемые из игры значения. Программист из-под палки собственной кровью пишет ночами максимально запутанный код под надзором полицая. Если код получается слишком простой, полицай лупит бедолагу палкой куда попадет. Для пущей сложности код пишется сразу на ассемблере, то есть на языке, с которым нативно работает целевая консоль. Потом специальная программа перегоняет мнемоники в циферки, а специальный станок записывает эти циферки на носитель методом, уже рассмотренным ранее. Если программист долго писал недостаточно запутанный код, компилировать его в машинный его заставляют в уме. Вот почему опытные ромхакеры могут читать сплошной шестнадцатеричный код игр как книгу. Не исключено, что наиболее прожженные из них даже в жизни общаются исключительно наборами чисел. Ведь недаром число бит, соответствующее ширине шины, называется словом!
Чтение, расшифровка и выполнение написанного программистом осуществляется в гигантских количествах (тактовая частота процессора NES – 1,79 МГц, а процессора MegaDrive – 7,67 МГц, то есть, имеем дело с миллионами команд в секунду), поэтому желающему научить свою программу это делать обычно требуется найти самый быстрый способ. Вот основные методы:
Интерпретатор
Самый простой метод, который потом еще и отлаживать легко, но самый медленный, так как мы просто повторяем как есть действия процессора.
Рекомпилятор (двоичный транслятор)
Этот прием, в силу его сложности и возможных проблем, используется нечасто и только там, где без него совсем туго. Суть его в том, чтобы считывать кусок кода из игры и переводить (транслировать) его в двоичный код целевого процессора на лету – динамическая трансляция. Существует еще статическая трансляция, когда мы берем всю игру целиком и конвертируем перед запуском. Но это в эмуляторах практически не используется, так как, во-первых, бывает код, который модифицирует сам себя на лету (тогда его транслированная версия не будет актуальной), во-вторых, невозможно однозначно определить, где в игре код, а где данные, не нуждающиеся в трансляции (для этого надо ее запустить), в-третьих, транслировать обращения игры к параллельно работающим компонентам крайне сложно (особенно, если нам надо их синхронизировать). Если мы помимо трансляции еще и измеряем частоту обращений к каждому блоку кода и оптимизируем те, что чаще вызываются – это будет динамической рекомпиляцией. Вот она-то и используется чаще всего, так как позволяет достичь существенного прироста в скорости для систем вроде N64 и более поздних.
По сути, это будут функции работы с двумя видами данных – с внутренним состоянием процессора и с памятью, отражающей состояние всей остальной системы. Состояние процессора обычно называется контекстом, и в случае, если эмулятор имеет функцию сэйвстейтов, может быть всем скопом сохранено в файл вместе с прочей памятью.
Память
Обычно абсолютно все компоненты системы, к которым процессор может осуществлять доступ, имеют свои адреса в памяти. Описание того, какие адреса какому устройству соответствуют, называется картой распределения памяти (memory map). Есть случаи, когда устройства ввода/вывода мапятся не в память, а на порты, но консоли это редко используют, и обычно все доступно по единой карте адресов.
Вся память делится на регионы, соответствующие своим девайсам: ПЗУ, ОЗУ, порты ввода-вывода (их так называют, даже если они находятся в памяти),
видеопамять, регистры звука и так далее. Однако, регионы редко покрывают всю физически доступную память.
Так, адресное пространство N64 имеет протяженность в 4 ГБ, хотя реально занято лишь около 4 МБ. То есть между регионами существуют либо "дыры" – открытая шина (unmapped memory), либо зеркала.
В случае попытки доступа к первым, если система не использует никакую защиту от дурака, возвращаться будет число, которое было последним на шине данных. Если же мы имеем дело с зеркалами, то будем получать число, которое есть по этому же оффсету (смещение от начального адреса) в другом регионе. Это происходит не потому, что ячейки этих зеркал в системе реально есть и не используются, а потому что при доступе к памяти происходит неполное декодирование адреса.
Эмуляция памяти имеет меньше нюансов, чем эмуляция процессора. Программе просто говорят выделить столько памяти под каждый регион, сколько реально будет нужно. Чтение и запись осуществляют разными функциями, обычно проверяющими принадлежность к региону, права на запись и еще много того, на что хватит фантазии.
Бывают еще банки памяти. Например, когда физически доступная память не вмещается в отведенный регион. Так устроено подавляющее большинство игр для NES – они хранят больше данных, чем приставка может за раз прочитать. Тогда в картриджи встраивают специальные механизмы переключения памяти, которые подсовывают в видимую консоли область разные регионы РОМа. Конечно, образы игр сами по себе, ничего переключить не в состоянии, поэтому нужный функционал добавляется прямо в эмулятор, ведь он загружает как-раз-таки всю игру сразу, и потом уже манипулирует банками (и бутылками).
Графика
Компьютерная графика бывает двумерной и трехмерной. Из разновидностей двумерной графики в консолях использовалась растровая (в частности, пиксель-арт), причем строилась она из плиток (tile), размер которых обычно был 8 на 8 пикселей. Из этих тайлов составлялись слои, которые могли на экране двигаться независимо друг от друга – фон и спрайты. И так как память и быстродействие всегда были сильно ограничены в сравнении с желаемыми, ставился упор на то, чтобы, имея одновременно загруженными минимальное число тайлов, максимальное их число использовать повторно – отсюда знакомые всем повторяющиеся узоры в играх. Трехмерная графика в консолях обычно основана на полигонах. То есть система работает с трехмерным пространством, где каждый объект (если это не спрайт, как в Думе) состоит из вершин, ребер, граней, полигонов, поверхностей и накладываемых на эти модели текстур. И в соответствии с тем, насколько хорошо программист знает тригонометрию и законы физики, эти модели движутся в пространстве и взаимодействуют друг с другом, а мы все это видим глазами двухмерной камеры.
Само по себе рисование на экране компьютера тайлов – дело нехитрое. Задаем координаты каждому из них и двигаем по экрану. Эффект наложения создается благодаря альфа каналу, позволяющему нам задавать прозрачность, а спрайтам – иметь какие-то другие очертания, кроме прямоугольных. Цвета задаются через ARGB (в разных сочетаниях), то есть альфаканал, красный, зеленый и синий. Компьютер может работать и с цветовой моделью, которую использовали ранние консоли – YUV (канал яркости и два цветоразностных), но почему-то так никто не делает.
Проблемы же начинаются, когда мы пытаемся создать в эмуляторе изображение, идентичное оригинальному на консоли:
1. Невозможность выяснить, какие же в точности цвета нам эмулировать. Генерируемый консолью YUV сигнал проходит через декодер, превращаясь в RGB, испытывая искажения в яркости, насыщенности, контрасте, резкости. Цвета, выходящие из консоли, всегда отличаются от тех, что мы видим на телевизоре. Это даже описано шутливой (лишь отчасти) расшифровкой названия NTSC – never the same color.
2. Синхронизация параллельно работающих видео - и центрального процессоров. Для получения аутентичных растровых эффектов недостаточно синхронизировать их только раз в кадр, следует это делать как минимум в конце каждой строки.
Однако, для некоторых хитрых растровых эффектов синхронизация должна быть уже попиксельной. А для совсем упоротых игр (и в режиме отладки) – потребуется уже синхронизация каждый цикл!
3. Эмуляция ограничений графической системы, особенно тех, которые неинтуитивно использовались пытливыми умами разработчиков игр (действующих по принципу "не баг, а фича"). Произведения деятелей дэмосцены хорошо это иллюстрируют: заставить консоль выдавать то, что простой пользователь не мог и представить, для них дело чести. Если эмулятор неточный, он запорется как на таких демках, так и на играх, где эксплуатировались те же принципы.
4. Эмуляция особенностей системы, которые документированы плохо, противоречиво, ошибочно или вообще никак. Для полноты картины придется заниматься декапом (разбором и фотографированием под микроскопом) оригинальны чипов и построением симулятора их работы.
5. Сохранение при всем при этом вменяемой скорости работы эмулятора, не требующего топового компьютера. На старых машинах известный эмулятор Супер Нинтендо BSNES выдаст 60 кадров в секунду, только если не используется режим абсолютной точности.
Эмуляция видеопроцессора обычно состоит из трех этапов.
1. Декодирование формата данных о пикселе, используемого консолью (у каждой консоли он могут быть свой уникальный), в понятный целевому компьютера формат. То есть, из видеопамяти читаются биты и байты, обозначающие цвет каждого пикселя в тайле, и преобразуются в сигнал ARGB. Консоль может использовать разные принципы генерации цвета. Например, в видеопроцессор может быть вшита палитра доступных цветов, и игры будут использовать только индекс для указания номера нужного цвета в палитре (тогда эмулятор может отдельно хранить все цвета исходной палитры и так же по индексу подставлять их в нужный пиксель), а может быть так, что игры сразу используют формат RGB, если имеется поддержка сразу большого количества цветов (тут уж придется каждый раз их декодировать по отдельности). Также могут использоваться эффекты, создающие цвет, изначально в палитре недоступный. Вариантов крайне много, поэтому едем далее.
2. Составление воедино всех слоев для каждой строки. Здесь важны приоритеты спрайтов и фона, то есть порядок их расположения относительно друг друга (z-order). Это будет влиять на то, в каком порядке они рисуются в промежуточный буфер (массив байтов, хранящий значения итоговых цветов). Игры могут использовать это для хитрых эффектов, например, в игре Jackie Chan's Action Kung Fu сначала рисуются темные колонны в слое фона, потом яркие колонны с высоким приоритетом в слое спрайтов, потом спрайты персонажей с низким приоритетом (поверх ярких колонн). Когда все это пересекается, яркий спрайт колонны перекрывается спрайтом персонажа, а тот перекрывается фоновой колонной (так как у него низкий приоритет).
В итоге мы видим эффект силуэта. Также консоль может при большом количестве спрайтов в строке использовать их мерцание каждый кадр, чередуя спрайты, чтобы в итоге видно было их все.
3. Составленная из сканлайнов в целой кадр картинка (массив байтов) блитится (blitting) в итоговый буфер, который будет представлен пользователю. Это уже делается видеокартой целевого компьютера, используя методы DirectX или OpenGL. При этом, изображение может быть подвержено дополнительным эффектам (фильтрация), а также изменено в размере. Так как это делается видеокартой, процесс этот довольно быстрый, поскольку разгружается основной процессор. А есть ситуации, когда вообще всю генерацию картинки проще делать силами видеокарты, например отрисовка полигонов: современные карты заточены как раз на ускорение этого процесса, а также позволяют добавлять различную фильтрацию текстурам, сглаживать грани полигонов, увеличивать их масштаб, и еще тысячей способов улучшать картинку. Все это можно, конечно, делать и на стороне CPU, но получим огромное замедление. Хотя тогда результат не будет зависеть от новизны видеокарты.
Звук
Звук может вырабатываться двумя способами: генерация и сэмплирование. В первом случае, сам звуковой чип синтезирует тон, позволяя программисту (и, желательно, музыканту по совместительству) менять различные параметры этого синтеза (частота, громкость, скважность, фаза, форма волны). Во втором, в игру закатывается специальным способом закодированный отрезок звука нужной длительности (например, по одной ноте на каждый инструмент, либо звуковой эффект целиком), и приставка просто воспроизводит его на нужной высоте, зацикливая, если надо.
Важной особенностью звука в консолях является частота сэмплирования (дискретизации), то есть частота обновления амлитуды (громкости) сигнала. Вообще, слово «сэмпл» обозначает отрезок оцифрованного звука и относится как к целому звуковому фрагменту (массиву), так и к каждой минимальной его части (элементу массива). Аналоговый звук непрерывен и может передать любую частоту, но при оцифровке возможно лишь выхватывать отдельные значения амплитуды с ограниченной скоростью, поэтому цифровой звук всегда дискретен (гуглим). И чтобы воспроизвести звук нужной частоты, нам надо обновлять амплитуду сигнала с частотой вдвое большей. Например, сэмпл с частотой дискретизации 40 кГц может содержать звук с частотами до 20 кГц.
Самым простым способом эмуляции звукового чипа-синтезатора (PSG, programmable sound generator) будет запись сэмплов в буфер на той частоте дискретизации, на которую способен сам чип. Например, треугольный канал приставки NES способен выдать максимальную частоту тона 55,9 кГц, что потребует частоты дискретизации 111,8 кГц. Однако, работать с такими значениями непрактично (да и услышать такие частоты невозможно), поэтому все эмуляторы применяют разные методы передискретизации, то есть конвертируют сигнал, меняя частоту его дискретизации на традиционные значения (44,1 кГц, 48 кГц, 96 кГц). Могут применяться еще и дополнительные фильтры на усмотрение автора, так как оригинальный звук тоже всегда подвергается фильтрации внутри консоли.
Генерация квадратной волны (стандартной для 8-битных консолей) сводится к записи значения амплитуды в буфер в течение времени, соответствующего длине волны. Таймер, считающий, сколько должен длиться период волны относительно циклов CPU, в разных консолях разный.
Более совершенные звуковые чипы были способны на частотно-модуляционный (FM) синтез. То есть берется волна определенной формы (обычно синусоидальная) и ее частота модулируется амплитудой другой волны.
В итоге частота несущей волны (carrier) будет меняться в точном соответствии с изменениями амплитуды волны модулирующей. Если частота модулирующей волны низкая, мы услышим изменение высоты тона у несущей волны. Если же частота достаточно высокая, мы услышим изменение тембра.
Несколько замечаний:
1. Технология частотной модуляции, использованная Ямахой для синтеза звука и представленная миру под именем FM синтеза, была на самом деле фазовой модуляцией.
2. При достаточно гладких модулирующих функциях формы итоговых сигналов фазовой и частотной модуляции практически идентичны.
3. Так как фазовую модуляцию проще реализовать программно, в эмуляторах используется именно она, даже если там ее назвали FM синтез.
4. Количество волн, последовательно модулирующих друг друга (операторов), может быть любым. FM синтезатор MasterSystem позволяет смешать таким образом только две волны, а мегадрайвовский – до четырех.
Наконец, простейшая система - это сэмплеры. Там уже не требуется ничего генерировать на лету, все сводится к манипуляции готовыми потоками данных. Предварительно их может понадобиться переконвертировать из одного из множества форматов, в зависимости от того, с каким из них работает эмулируемая система, в тот, с которым работает целевая система.
NES может использовать два вида сэмплов: однобитные и семибитные. В первых изменение уровня исходного сигнала закодировано одним битом, то есть он либо понижается на одну ступень, либо повышается. Для этого сравниваются текущие уровни исходного и закодированного сигналов, и если исходная амплитуда выше – в закодированный следующим пишется 1, иначе 0. Такие сэмплы практически ничего не весят, но звучат относительно разборчиво. Частота дискретизации у них стандартная, так как NES способна в фиксированные интервалы времени сама воспроизводить новую порцию битов. Семибитные же сэмплы имеют такую точность соответствия исходному сигналу, какую захочет разработчик, так как, во-первых, все закодировано 7-ю битами (128 уровней), во-вторых, автор игры сам решает, когда воспроизвести новый отрезок сэмпла, хоть каждую инструкцию.
В более поздних консолях, где уже не требовалось так параноидально экономить, форматы сэмплов были ближе к тем, которые известны современным компьютерам.
После генерации сэмплов каждого инструмента их необходимо смикшировать. Это делается сложением амплитуд всех исходных каналов. Однако, это не стадион, где может одновременно кричать несколько тысяч человек, и верхней границы громкости не будет. В цифровом звуке она есть, это уже упомянутая битность. Допустим, мы работаем с 8-битным звуком. Если в результате сложения каналов получается амплитуда больше максимально допустимой (например, 400), начинаются сложности, так как приходится придумывать способы сохранить информацию и донести в максимально точной форме. Просто делить итоговую амплитуду, пока она не влезет, нельзя: каждый канал начнет звучать тише, и потеряются нюансы, на которые он тратит все доступные ему уровни громкости (256). Можно просто ограничивать амплитуду каждого сэмпла до максимально допустимой (clamp), но так тоже теряются характеристики звука. То же самое с отсечением всех чрезмерных амплитуд. Какое решение будет универсальным? Матан! И мы его снова пропустим.
Наконец, для выдачи всех сгенерированных и обработанных сэмплов человеку обычно используется кольцевой буфер. То есть ядро пишет данные в массив, при достижении последней ячейки оно начинает записывать снова с первой, а в это время звуковой драйвер с нужным интервалом считывает из этого буфера данные, играя пользователю чиптюновую версия Бетховена. Или Ламбаду.
Есть такие драйвера звука, которые позволяют вообще всю скорость работы эмулятора привязать к частоте сэмплирования: с одной стороны ядро пишет нужное количество сэмплов в буфер и ждет, когда драйвер их прочитает, и только после этого продолжает эмуляцию, с другой стороны драйвер забирает сэмплы и воспроизводит их со стабильной частотой, не допуская пауз. Так работают эмуляторы, использующие кроссплатформенную библиотеку SDL.
Ввод
На уровне ядра ввод обычно реализуется функциями записи в память (в регистры ввода), и раз в кадр ожидается поступление от клиента информации о том, какие кнопки нажаты (каждая кнопка обычно кодируется одним битом), и каково состояние аналоговых устройств управления. Тогда игра, читая соответствующие адреса памяти, сама все сделает как надо.
Поддерживаемые консолью устройства ввода могут быть самыми экзотическими, как и поддерживаемые клиентом (при помощи соответствующего драйвера), поэтому перестанем забивать себе голову и перейдем к заключению.
Заключение
Если мама разрешит, в следующий раз я расскажу вам об устройстве клиента и инструментах эмулятора, которые выгодно отличают его от консоли. А можете сами это все изучить в качестве домашнего задания.
Журнал: DF Mag #7