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



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

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

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

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


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


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



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

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


После этого мне потребовалось выбрать библиотеку для Python, которая бы могла:
  • Работать с несколькими источниками аудио.
  • Давала высокоуровневый интерфейс для работы с ними.

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

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

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

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




Лучшей библиотекой для 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 можно получить прикольный, винтажный и компактный приёмничек, который можно таскать с собой в сумке.

ДУП


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





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





Raspberry Pi


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





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



Динамик


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



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



Комментарии (25)


  1. ionicman
    10.08.2018 15:32
    -1

    ДУП? Серьезно? :D

    Вообще-то это «rotary encoder», в простонародье просто «энкодер», а для ДУП устоявшиеся значения в русском языке: «дистанционный указатель положения» и «дочернее унитарное предприятие». Просто есть некоторые слова, которые не переводятся на русский и идут «как есть».

    Естественно, это никак не влияет на данный DIY — получилось хорошо :)


    1. GeMir
      10.08.2018 16:17

      ДУП?
      И «доска для прототипирования» (breadboard) :)


    1. Shpakov
      10.08.2018 17:46

      Мне кажется, это от внешнего окружения зависит (где что используют и к чему человек привык) — у нас на предприятии как раз термин «ДУП» употребляют, так мне другие варианты до Вашего комментария и в голову не приходили…
      Хотя соглашусь, в хоббийном проекте «энкодер» кажется более уместным, «ДУП» как-то официально звучит… Но есть и более забавное обозначение — «ПУП» — «преобразователь угловых перемещений». Слышал: «Что-то ПУП беспокоит...», «Опять с ПУПом проблема...» и т.д.


      1. Andy_Big
        11.08.2018 12:19

        Все же «датчик углового положения», на мой взгляд, применимо лишь к датчикам абсолютного угла.


    1. Nondv
      11.08.2018 17:39

      я бы еще слово "взломал" заменил англицизмом "хакнул", т.к. "взломал" не особо подходит по смыслу.


      Терминология, конечно, вещь не самая приятная:(


  1. sainomori
    10.08.2018 15:43

    Ну гипотетически, зная время от включения устройства необязательно крутить все треки одновременно. Можно подключать вторую дорожку только при переключении начиная от определённого момента дорожки.

    Ну и не хватает аутентичных радио-шумов при переключении =)


  1. Jeka178RUS
    10.08.2018 15:50

    Прикольно! А как же добавить радиопомехи при смешивании двух радиостанций?
    P.S. Я буду обновлять комментарии, честно


  1. vyacheslavteplyakov
    10.08.2018 15:56

    Да, просто добавить еще один канал с «помехами» и сунуть его в «миксере» между каналами, чтобы всплывал в середине перехода, будет круто вообще.


  1. Wolframium13
    10.08.2018 16:17

    Радио без помех при переключении — не радио. Небольшое ПШШШ как в игре было бы очень к месту.


  1. lizarge
    10.08.2018 16:46
    +4

    Ну очевидно что играть все треки сразу ради такой задачи это выстрел из атомной лодки в муху, не говоря уже про Raspberry Pi и пайтон. Все можно было сделать проще в сотни раз, но раз уж использован такой нелепый способ, можно было и помехи нормально имитировать при переключении, или даже реальные включать. Не говоря о том что с такими раскладами можно и из интернета звук брать, и еще много чего.
    Короче идея на 5, нада делать


    1. Konachan700
      10.08.2018 18:31

      Да, в текущем варианте хватило бы мелкой ардуины, шилда с карточкой и mp3-кодека. Всё есть на али в виде шилдов, на всё есть готовые либы в ардуино.
      А вот сама идея понравилась.


    1. dlinyj
      10.08.2018 19:10

      Вот, вопрос, как с помощью Mpd эмулировать шумы?


    1. PavelNN
      11.08.2018 14:45

      Сама идея очень заинтересовала, если подскажете как можно упростить в сотню раз — буду благодарен.


  1. Dvlbug
    10.08.2018 18:24

    А почему не подключить +5v, GND к пинам 4,6? Должно запуститься.
    И все-таки жалко радиоприемник, я бы лучше его восстановил или переделал внутрянку на современные частоты


    1. vinny496
      11.08.2018 05:19

      Так как бы винтажные частоты в винтажном приёмнике вполне себе работают, не надо ничего переделывать.


  1. dlinyj
    10.08.2018 19:09

    Я такую штуку делал ;)


  1. id_potassium_chloride
    10.08.2018 19:33

    А почему нельзя использовать Raspberry Pi в качестве радиостанции с помощью PiFM? На малой мощности эта радиостанция не помешает другим, радиоприёмник останется цел, проблема с генерацией шумов решится сама собой.


    1. Dvlbug
      10.08.2018 21:50

      Судя по нанесенным рискам на ползунке радиоприемника длина волны (не могу вспомнить, как правильно называется), выше чем выдает малинка


  1. rw6hrm
    10.08.2018 22:18
    +1

    … сломаный винтажный приёмник не прощу! ;)


  1. POS_troi
    11.08.2018 00:16

    Вот чего для меня остаётся загадкой — зачем он припаял провод питания?
    Судя по фоткам, вполне можно было втыкнуть нормальный микроюсб штекер (места там хватает, а если взять угловой то вообще шик) и уже от него вывести куда нужно и что нужно. Микроюсб штекер разборный, да и отрезанный откуда-то и примотанный изолентой лучше будет.

    Реально единственное, очень стрёмное решение в это конструкции — питание на соплях.


  1. Barabek
    11.08.2018 00:20
    +1

    Да вы упороты! :)


  1. Vbeerby
    11.08.2018 08:29

    Динамик стоило все-таки заменить и сделать для него корпус, тем более что место позволяло.
    На что-то типа такого(120-20КГц):

    Заголовок спойлера
    image


  1. SokoloffA
    11.08.2018 14:45
    +1

    А мне жалко, что родное колесо настройки выкинули.


  1. Big-Boss
    11.08.2018 15:24

    А не проще было распихать треки по папкам-радиостанциям и в зависимости от прошедшего времени открывать соответствующий трек с нужным сдвигом?
    А лаг на открытие как раз скрывать белым релктовым шумом!


  1. vvzvlad
    11.08.2018 20:15
    +2

    Какая мерзость