Просмотр полной версии : Общий подход к эмуляции биперного звука
CityAceE
19.04.2020, 08:22
Уже замечено, что во многих эмуляторах, которые сделаны в учебных или демонстрационных целях, отсутствует эмуляция звука. Думаю, что это не с проста :) Ведь необходимо в реальном времени равномерно формировать нужные данные и непрерывно подкидывать их в звуковой буфер. Не очень простая задача.
Свой первый звук я выводил в эмуляторе ZX-Pilot для PalmOS. Там я особо не заморачивался и напрямую на ассемблере перенаправлял команды OUT Z80 в соответствующий порт Paml-устройства. При таком подходе, низкоуровневом программировании и полном отсутствии каких-либо задержек в ядре эмулятора результат превзошёл мои ожидания.
Второй раз я сделал звук в эмуляторе "Ну, погоди" на Python. Там я использовал подход синхронизации по звуку по подсказке IgorR76 (https://zx-pk.ru/members/8624-igorr76.html). То есть процессор работает заполняя звуковой буфер, а потом, как он заполнится выводятся звук и сформированная картинка. Но там был просто канонический случай. Частота эмулируемого процессора 16384 Гц, 1 команда = 1 такт. Поэтому я просто выставил частоту дискретизации звука 16384 Гц и последовательно после выполнения каждой команды заполнял буфер единицами и нулями, в зависимости от состояния порта. Всё идеально.
И вот я, наконец, созрел прикрутить звук вот сюда (https://zx-pk.ru/threads/29897-spycialist-emulyator-pk-spetsialist-na-python.html) по тому же методу. И тут у меня наступил затык. В силу того, что частота процессора существенно отличается от частоты сэмплирования, да ещё и команды процессора имеют разное время исполнения, потребуются некоторые преобразования.
Например, частота КР580ВМ80А - 2 МГц, а частота сэмплирования 44100 Гц (ну или 22 100 Гц - не так важно). Получается, что на 1 Гц звука приходится 2 000 000 / 44 100 = 45,35 такта процессора. Во-первых, если синхронизироваться по звуку, то придётся менять частоту виртуального процессора - 45 * 44 100 = 1 984 500 Гц. Думаю, что это пережить можно. Но не совсем понятно как правильно 45 единиц и нулей преобразовать в одно число 0-255? Ведь за 45 тактов значение порта может несколько раз поменяться и тут не будет чистых нулей и единиц.
В голову приходит и другой вариант: писать в буфер все данные с частотой 2 МГц за какой-то короткий промежуток времени, а потом этот буфер перед проигрыванием какой-нибудь готовой процедурой (ну или своей) преобразовывать в те же 44 100 Гц. Но тут я боюсь, просто буду не успевать производить такие манипуляции, а тем более на Python.
Пока я веду речь о простом однобитном звуке.
В общем, подскажите, как делать правильно, быстро и чтобы качество звука было на высоте?
P.S. Пока всё делаю на Python, чтобы разобраться с вопросом. А конечная цель: сделать то же самое на ARM-ассемблере. Но это в отдалённом будущем.
В v06x звук тактирует видео.
Все семплы с таймера не пишутся в буфер, это был бы очень большой буфер, а пропускаются через ресемплер, который превращает их в 48000 отсчетов в секунду и ресемплированный сигнал уже набивает буфер.
Цифры такие: частота ви53 1.5мгц. Коэффициент пересчета для ресемплера ×5 ÷156, получается ~48077, годится.
Ресемплер самая интересная вещь. Мне в его написании очень помогли ivagor и бумага "Introduction to Digital Resampling" by Dr. Mike Porteous (гуглится).
Я думаю, что для начала можно взять scipy.signal.resample.
Но не совсем понятно как правильно 45 единиц и нулей преобразовать в одно число 0-255?
кол-во едениц * 255 / 45
Или тут какие-то подводные камни есть?
CityAceE
19.04.2020, 12:10
Все семплы с таймера не пишутся в буфер, это был бы очень большой буфер, а пропускаются через ресемплер
Собственно, как я и предположил во втором варианте. Видимо, придётся попробовать так, но боюсь, что на тормозном Python не удастся добиться приемлемого результата.
Мне в его написании очень помогли ivagor
Иван прям везде успевает :) Я когда упёрся в этот вопрос, тоже первый, о ком я подумал был как раз Иван :)
кол-во единиц * 255 / 45
Или тут какие-то подводные камни есть?
Опять же, такой вариант первым пришёл мне в голову. Но я не уверен, что это будет работать так, как должно. Вот я и хотел узнать, есть ли какие-то подводные камни?
Я сейчас проиллюстрирую примером с идеальными условиями из "Ну, погоди!":
Вот с такими значениями инициализируется звуковой буфер:
# Инициализация звукового буфера, должна быть раньше всех инициализаций!
# pygame.mixer.init(frequency=16384, size=8, channels=1, buffer=1024)
pygame.mixer.init(16384, 8, 1, 1024)
А вот так буфер заполняется:
sound = cpu.exec_op_code()
buffer[i] = sound * 255
То есть после исполнения каждой команды процессора снимается значение порта, отвечающего за звук. Если там 0, то в буфер пишется 0, а если единица, то 255. И потом, по достижении конца буфера (1 кб), выводится 8-битный одноканальный звук с частотой дискретизации 16 кГц.
Будет ли корректным результат, если я сложу 45 значений порта (единицы и нули), умножу их на 255 и разделю на 45, откинув дробную часть? Видимо, пока не проведу эксперимент, не узнаю. Однако для этого придётся поменять логику ядра эмулятора. Сейчас в эмуляторе виртуальный процессор тактируется экраном: экран рисуется 50 раз в секунду, а между выводами экрана отрабатывается 2 000 000 Гц / 50 = 40 000 тактов и ждёт отрисовки следующего экрана.
Собрать здоровенный буфер и скормить его на обработку scipy будет относительно быстро, а качество ресемплера в scipy.signal.resample_poly на высоте. Делать ЦОС прямо на Питоне семпл за семплом будет медленно. С другой стороны, если это прототип, то его рилтаймовые характеристики неважны. Пока отлаживаешь алгоритм пусть он работает хоть 10 минут за 1 секунду реального времени. Оптимизировать надо когда отлажен принцип работы.
Все сложить и поделить -- это тоже ресемплер, только очень примитивный. Обычная "таймерная музыка" будет звучать шершаво, но терпимо, а вот ШИМ превратится в шум. С практической точки зрения это небольшая беда, потому что не так много ШИМ-ных программ написано для 8080. Но это неплохой бенчмарк качества ресемплера. Кроме того, качественные эмуляторы вдохновляют людей писать интересные вещи.
NEO SPECTRUMAN
19.04.2020, 15:34
Пока я веду речь о простом однобитном звуке.
например можно сделать так
сразу эмулировать работу динамика
установка бита вывода звука
включает инкремент переменной громкости каждую команду на Коэфицент*Количество тактов команды
у инкрементилки ограничение по максимальному значению
превышая которое будет установленно обратно максимальное значение
сброс бита звука
включает декрементилку переменной громкости каждую команду на Коэфицент*Количество тактов команды
у декрементилки ограничение по минимальному уровню
превышая которое будет установленно обратно минимальное значение
после такого можно будет писать в буффер намного меньше значений для более простого ресемплирования (тк алиасинг серанвно будет)
или может даже не репрессировать вообще
(если не будет беспокоить писк)
или же можно организовать подобную эмуляцию динамика над большим буффером
возможно это будет более оптимальным вариантом
меняя коэфициент методом научного тыка
можно будет менять частоту среза высоких
и подобрать более подходящий тембр
ну и для лучшего эффекта еще нужно эмитировать конденсатор в цепи
интуитивно понятная картинка как оно работает
https://jpegshare.net/images/24/6b/246b8a9c7cc75579cc49ec0c9bc9b78a.png
правда тогда нужно усложнять предыдущий алгоритм
хотя можно забить и пустить и пост обработкой к первому
CityAceE
19.04.2020, 16:31
например можно сделать так
Да, тоже вариант. Спасибо.
Обычная "таймерная музыка" будет звучать шершаво, но терпимо, а вот ШИМ превратится в шум.
А если учесть интересную идею CityAceE-а:
менять частоту виртуального процессора - 45 * 44 100 = 1 984 500 Гц
Может будет не так уж всё и плохо?
Может будет не так уж всё и плохо?
Дело по-моему не в кратности частот, а в подходе с нарезанием на кусочки. На стыках кусочков получаются звуки, которых нет. От этого получается хрюкотанье, которое практически невозможно отфильтровать.
Если я правильно понимаю метод NEO SPECTRUMAN-а, это суть интегратор. Интегратор годится для преобразования ШИМ в ИКМ, то есть то, что мы хотим слышать. Выход интегратора можно семплировать в буфер с частотой аудиокарты и такой результат имеет шансы получше. Не попробуешь — не узнаешь.
NEO SPECTRUMAN
20.04.2020, 16:58
Выход интегратора можно семплировать в буфер с частотой аудиокарты
до
НО
если кто то будет пытаться делать ШИМ на бипере
высокая не кратная частота ШИМ-а серавно пролезет
а если не ресемплиовать хоть немного хотябы простое 2х
вылезет алисаниг
хотя и тихий
самое главное
что такая шняга легко реализуется целочисленно
- - - Добавлено - - -
Дело по-моему не в кратности частот,
в кратности тоже
эмуляторы изза этого любят пищать
ну и как итог
don't use emulators, their beeper emulation SUXX
а "частота нарезки" не такая высокая чтобы пищать
Дело в том, что этот велосипед уже изобретен. Входной сигнал растягивается вставкой нулей в UP раз, интерполируется ФНЧ и затем из полученного сигнала берется каждый DOWN семпл. Получается передискретизация с коэффициентом UP/DOWN. Этот метод проверен, работает, его можно попробовать в оффлайне в Аудасити, Питоне, Матлабе, Октаве в утилите sox и наверняка еще в куче модных программ. В v06x он реализован в рилтайме: для современного компьютера и даже телефона это нагрузка посильная, а писка, по крайней мере для ушей нашего возраста, нет.
Но всегда интересно сравнить свой велосипед с уже имеющимся, поэтому это не отменяет моего личного например любопытства к методу с примитивным интегратором.
Вариант с интегратором - частный случай общего подхода, интегратор тоже давит верхние частоты, это один из возможных вариантов ФНЧ (далеко не лучший, но простой при описанной реализации). Но по хорошему перед реализацией все же стоит проверить частотные характеристики, насколько портится сигнал в основной полосе и насколько давятся лишние повторения спектра.
Дело по-моему не в кратности частот
А мне казалось, что именно в этом. При некратных частотах, в соседних сэмплах получается чуть больше, чем надо в одном и чуть меньше в другом. Отсюда и хрипы.
Но в одном ты прав - пока не попробуешь, не узнаешь.
Lethargeek
20.04.2020, 23:05
NEO SPECTRUMAN дело говорит - чистый однобитный звук без обработки будет хрипеть
(уж не знаю, в чём причина, в несовпадении частот и/или в свойствах современных динамиков)
Powered by vBulletin® Version 4.2.5 Copyright © 2026 vBulletin Solutions, Inc. All rights reserved. Перевод: zCarot