- PVSM.RU - https://www.pvsm.ru -

Из однобитной музыки — мяубитную

Прошлая статья [1], посвящённая обучающей плате Meowbit [2] и реализациям Python для неё, завершалась упоминанием неспособности CircuitPython проигрывать музыку одновременно с игрой: писать на Python обработчики прерываний CircuitPython не позволяет, а без этого – задержка на время перерисовки экрана (порядка 0.15 с) «подвешивает» звук. Тем не менее, фоновый звук бывает нужен достаточно часто, и для большинства поддерживаемых плат [3] (100 из 189) CircuitPython включает модуль audioio либо audiopwmio, реализующий фоновый звук родными для платы способами. К сожалению, для Meowbit (и вообще для плат на основе STM32 [4]) не реализован ни тот ни другой модуль; но в opensource-проекте это дело поправимое.

Из однобитной музыки — мяубитную - 1 [5]
Найдите пасхалку в фото

Прежде всего: почему для проигрывания звука есть два разных модуля с полностью одинаковыми API, и на разных платах поддерживается либо тот, либо другой?

Вот как в аудиоредакторе (например Audacity) выглядит ⅒ секунды обычного (16-битного)
WAV-файлa:

Из однобитной музыки — мяубитную - 2

Значение плавно меняется в пределах примерно от −0.2 до +0.2 «условной единицы». Если таким же образом менять напряжение, подаваемое на электродинамический громкоговоритель [6], то так же плавно будет колебаться мембрана – примерно от 0.2 своего максимально возможного отклонения в одну сторону, до 0.2 отклонения в другую сторону. Модуль audioio реализует именно такое проигрывание звука – через ЦАП [7] плавно меняет напряжение на выводе, соединённом с динамиком.

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

Из однобитной музыки — мяубитную - 3

Таким способом невозможно передавать изменение громкости звука, но теоретически возможно передать все имеющиеся в нём гармоники – если параллельно с 32768-кратным понижением разрешения увеличить во столько же раз (т.е. до сотен мегагерц) частоту дискретизации. Маловероятно, что мембрана пьезопищалки сможет колебаться с такой частотой; но это можно использовать и в свою пользу – если научиться переключать напряжение на пищалке, когда мембрана на полпути, то можно издавать звуки промежуточной громкости! Поиск по патентам подтверждает, что люди действительно исследуют возможности использовать пьезопищалку таким образом. Мы в эти дебри углубляться не будем, и оставим обычную для WAV частоту дискретизации в десятки килогерц. Для музыки, где основные гармоники в районе килогерца, этого достаточно; речь, однако, превращается в едва разборчивый шум. Можете сравнить, как воспринимается использованный мной восьмисекундный образец звука, воспроизведённый на однобитной пьезопищалке: вначале оригинал, затем однобитная версия, затем запись Meowbit-а микрофоном.

Модуль audiopwmio реализует проигрывание звука через цифровой вывод посредством ШИМ [8]: однобитная аудиозапись превращается в последовательность задержек между переключением
вывода на противоположное значение.

Итак, общий план реализации audiopwmio для Meowbit таков:

  1. Переводим переданную пользователем аудиозапись в ШИМ-формат (список задержек между переключениями);
  2. В обработчике прерывания от таймера переключаем вывод и настраиваем таймер на задержку до следующего переключения. Этот код можно с минимальными изменениями позаимствовать из стандартного модуля pulseio, реализующего в точности то, что нам нужно – переключение вывода в соответствии с переданным списком задержек – но не позволяющего коду на Python выполняться параллельно с переключением.

Не сразу было очевидно, что надо позаботиться ещё об одном аспекте реализации – буферизации аудио. Мой тестовый восьмисекундный образец занимает 8·22050·2 ≈ 340 КБ – это втрое больше, чем всё ОЗУ Meowbit; следовательно, загружать его в память придётся по кускам. Стандартная реализация audiocore.WaveFile загружает WAV-файл кусочками по 256 байт, что соответствует 128 сэмплам или 5.8 мс времени проигрывания. Это значит, что в среднем каждые 5.8 мс audiopwmio должен будет запрашивать повторное наполнение буфера; нет выхода, кроме как разместить этот вызов в том же самом обработчике прерывания от таймера – иначе перерисовка экрана может задержать наполнение буфера на добрую сотню миллисекунд. Проблему это, однако, не решает до конца: что произойдёт, если прерывание от таймера случится во время перерисовки экрана? Экран Meowbit подключён через шину SPI [9], флеш-диск – через неё же, значит обращение ко флешу во время перерисовки экрана всё равно невозможно!

В результате получилась реализация audiopwmio, способная воспроизводить аудиозаписи из памяти (или процедурно генерируемые) в максимально достижимом на Meowbit качестве; но аудиозаписи из файлов воспроизводятся только при отсутствии одновременных с этим обращений к экрану и ко флешу. Для звукового сопровождения несложных игр этого вполне хватит. PR с моей реализацией уже больше недели дожидается ревью, и когда audiopwmio для Meowbit появится в официальной версии CircuitPython – неизвестно; но это не мешает любым желающим самостоятельно скомпилировать себе CircuitPython с моей добавкой.

Из однобитной музыки — мяубитную - 4

Автор: ruvds

Источник [10]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/362831

Ссылки в тексте:

[1] Прошлая статья: https://habr.com/ru/company/ruvds/blog/545732/

[2] обучающей плате Meowbit: https://www.kittenbot.cc/products/meowbit-codable-console-for-microsoft-makecode-arcade

[3] большинства поддерживаемых плат: https://circuitpython.readthedocs.io/en/latest/shared-bindings/support_matrix.html

[4] STM32: https://ru.wikipedia.org/wiki/STM32

[5] Image: https://habr.com/ru/company/ruvds/blog/549240/

[6] электродинамический громкоговоритель: https://ru.wikipedia.org/wiki/%D0%AD%D0%BB%D0%B5%D0%BA%D1%82%D1%80%D0%BE%D0%B4%D0%B8%D0%BD%D0%B0%D0%BC%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B9_%D0%B3%D1%80%D0%BE%D0%BC%D0%BA%D0%BE%D0%B3%D0%BE%D0%B2%D0%BE%D1%80%D0%B8%D1%82%D0%B5%D0%BB%D1%8C

[7] ЦАП: https://ru.wikipedia.org/wiki/%D0%A6%D0%B8%D1%84%D1%80%D0%BE-%D0%B0%D0%BD%D0%B0%D0%BB%D0%BE%D0%B3%D0%BE%D0%B2%D1%8B%D0%B9_%D0%BF%D1%80%D0%B5%D0%BE%D0%B1%D1%80%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C

[8] ШИМ: https://ru.wikipedia.org/wiki/%D0%A8%D0%B8%D1%80%D0%BE%D1%82%D0%BD%D0%BE-%D0%B8%D0%BC%D0%BF%D1%83%D0%BB%D1%8C%D1%81%D0%BD%D0%B0%D1%8F_%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D1%8F%D1%86%D0%B8%D1%8F

[9] шину SPI: https://ru.wikipedia.org/wiki/Serial_Peripheral_Interface

[10] Источник: https://habr.com/ru/post/549240/?utm_source=habrahabr&utm_medium=rss&utm_campaign=549240