[Перевод] Делаем радиостанцию из GTA: San Andreas

Хотелось ли вам переключаться между радиостанциями так, как вы делали это в Сан-Андреас?

ed918b5c0863ea3973f9128ef2f6e03f.jpg

Саундтрек из GTA прославился хорошим выбором музыки и забавными вставками. Компания Rockstar проделала отличную работу, создавая радиостанции для этой игры — кстати, музыку из Vice City и San Andreas можно даже купить в виде наборов CD.

Чтобы сделать её прослушивание более приятным — и кое-чему обучиться, я решил взломать радиоприёмник так, чтобы он принимал игровые радиостанции.

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

Давайте начнём!


1. Оборудование


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

9213b9f02fdcc18e91e79850e200ed9d.jpg

Но для проигрывания десятков файлов одновременно требуются мощные мозги, поэтому я остановился на Raspberry Pi. Я уже использовал его для других проектов, и уверенно чувствую себя при работе с ним. А в качестве языка программирования я выбрал Python, просто потому, что у меня было такое настроение. А нужны ли другие причины?…

2. Программный миксер на Python


После этого мне потребовалось выбрать библиотеку для Python, которая бы могла:

  • Работать с несколькими источниками аудио.
  • Давала высокоуровневый интерфейс для работы с ними.


Для постройки виртуального радио мне нужно только загружать несколько файлов сразу и управлять громкостью воспроизведения. По сути, в реальности это и происходит: каждая станция — источник аудио, а ручка настройки меняет громкость этих источников, с точки зрения слушателя.

После тяжёлого и продолжительного тестирования различных вариантов (pygame-mixer, python-sounddevice, puredata с патчем для миксера), я остановился на swmixer. Он даже умеет воспроизводить файлы потоково, не загружая их в память целиком, что было весьма удобно для меня, поскольку я хотел для каждой станции объединить всю музыку в один файл. Мне пришлось сделать форк этой библиотеки и исправить в ней ошибку, поскольку её уже не поддерживали.

Я решил использовать Raspberry Pi 3, поскольку модель 2B каким-то образом зажимала вывод аудио. Не став разбираться с тем, почему это происходит, а просто удовольствовавшись тем, что на новой плате всё работает, я перешёл к следующему шагу.

3. Высокоуровневый датчик угла поворота (ДУП) (познакомьтесь с pyKY040)


046e6464993ce7e1702ca262a68869d4.png

Лучшей библиотекой для Python на тот момент для ДУП KY040 была библиотека под названием KY040, но она не совсем подходила под мои нужды, к тому же, мне хотелось попробовать сделать свой собственный, первый, настоящий модуль для Python, поэтому я и написал pyKY040.

Он предоставляет:

  • Колбек на увеличение значения.
  • Колбек на уменьшение.
  • Колбек на изменение.
  • Колбек нажатия кнопки.


Настройки:

  • Режим шкалы (внутренний счётчик изменяется в рамках границ от X до Y, передаётся в качестве аргумента колбек-функциям).
  • Режим кольцевой шкалы (от X до Y, затем снова X).
  • Настройка величины шага на шкале.


Он позволил мне делегировать логику работы ДУП и сконцентрироваться на том, что происходит, когда я взаимодействую с ним.

К ДУП в основном коде относятся только следующие строчки:

tuning_encoder = pyky040.Encoder(CLK=17, DT=27, SW=22)
tuning_encoder.setup(scale_min=MIN_VFREQ, scale_max=MAX_VFREQ, step=1, chg_callback=vfreq_changed)
tuning_thread = threading.Thread(target=tuning_encoder.watch)

volume_encoder = pyky040.Encoder(CLK=5, DT=6, SW=13)
volume_encoder.setup(scale_min=0, scale_max=10, step=1, inc_callback=inc_global_volume, dec_callback=dec_global_volume, sw_callback=toggle_mute)
global_volume_thread = threading.Thread(target=volume_encoder.watch)

tuning_thread.start()
global_volume_thread.start()

4. Виртуальное радио (код)


Теперь я мог проигрывать файлы и у меня появился интерфейс для работы с ДУП. Настало время написать код для виртуального радио.

Работает он, как виртуальный диапазон радио. На определённых виртуальных частотах (или вч) можно услышать звук — это каналы swmixer. Между двумя вч слышно смесь двух аудиоисточников.


Chn 1          Chn 2          Chn 3          Chn 4         Chn n        
   |              |              |              |             |         
   |--------------|--------------|--------------|-------------|         
                                                                        
   <---------------------------------------------------------->         
                         виртуальная частота                              

Виртуальная частота на самом деле представляет собой просто целое число, которое увеличивается или уменьшается в зависимости от вашего взаимодействия с ДУП.

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


                            VOLUME                           
      /-\                                           /-\     
   /-  | --\                                     /-- | --\  
--     |    --\                               /--    |    --
       |       --\                         /--       |       
       |          -\                     /-          |       
       |            --\               /--            |       
       |               --\         /--               |       
       |                  --\   /--                  |       
       |                     /--                     |       
       |                  /--   --\                  |       
       |               /--         --\               |       
       |            /--               --\            |       
       |          /-                     -\          |       
       |       /--                         --\       |       
 -\    |    /--                               --\    |    /- 
   --\ | /--                                     --\ | /--   
   -------------------------------------------------------   
       |                    vfreq                    |       
                                                             
вч предыдущего                           вч следующего
канала                                          канала

Если мы применим этот пример к каналу n-1, он пересечётся с каналом n, в результате чего вы получите смесь аудио из двух этих источников.

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


Когда вч изменяется (сработал ДУП)
    -> получить значения громкости для обоих аудиоканалов
    [
        -> получить значения громкости для обоих аудиоканалов
        [
            вычислить близлежащие каналы вч (верхний и нижний)
            если это не тот канал, его громкость равна 0
            иначе вычислить громкость канала вч
        ]
    ]
    -> назначить каналам громкость

Все детали есть в коде. Начните с конца файла, где описаны колбеки для ДУП, а потом идите по следу.

5. Взламываем корпус радиоприёмника


Донором выступил радиоприёмник моего дедушки, Optalix TO100. За пределами Франции, полагаю, найти его будет нелегко. Примерно за €20 можно получить прикольный, винтажный и компактный приёмничек, который можно таскать с собой в сумке.

ДУП


Сами по себе ДУП влезали как раз так, что можно было закрыть корпус –, но после подсоединения проводов-джамперов конструкция оказывалась слишком большой. Пришлось удалить оригинальные пластиковые коннекторы и заменить на термоусадку, чтобы её можно было сгибать.

45ba79f49c6aab4c8aeadfb0034e11e4.jpg

d4d52632ad8928335447d1022a62df54.jpg

У тех ДУП, что я себе купил, не было резьбы — пришлось импровизировать с присоединением их к корпусу при помощи картонки и термопластичного клея. Клей решает, как всегда.

12024d817368438cc6ddc9141c47f0c0.jpg

b9c9bca80bd00dff9f891c9421b53629.jpg

Raspberry Pi


Разъём питания я перенёс на корпус, вместо того, чтобы втыкать кабель прямо в Pi — в результате я мог выбирать, как размещать компьютер в корпусе, не думая о том, куда именно на корпусе надо пристроить разъём питания.

21ec4b22123a4d830526a47004bf7565.jpg

8df883c1f6508073606d7da5dbc629aa.jpg

Я использовал кабель с разъёмами micro-USB «мама» и «папа», но чтобы освободить больше места, я зачистил кабель и напрямую припаял к Pi — плюс к PP2 и минус к разъёму, который заземлён (другой вариант — PP5). И обязательно клея побольше, чтобы не отломалось.

20e863776cd0feda62439b4f1ec54244.jpg

Динамик


Динамик — оригинальный на 5Вт от приёмника TO100. Он соединён с усилителем, который соединён с аудио штекером 3,5 мм, который воткнут в дешёвый USB ЦАП.

ddef0f23aa556a568fb6c7eb24ff85e9.jpg

Все соединено через доску для прототипирования и готово закрыться на веки вечные.

0b62b75f18d8764f564f53f45e746b25.jpg

924c43261eb0980da06fe1b61998d6ca.jpg

© Habrahabr.ru