Coin web de Frédéric Péters

fpeters@0d.be

Switch antenne, version avec jack

20 octobre 2023, 10:07

L’épisode était il y a plus de deux ans (Switch antenne), je commence donc par un rapide résumé : à la radio on a deux studios et un dispositif pour déterminer quel studio va envoyer son signal pour la diffusion, le studio 1, le studio 2, ou aucun, la diffusion automatique prenant alors la place. C’est géré via un arduino, avec de la petite électronique pour deux parties distinctes : les boutons et leds permettant de faire la sélection et d’afficher celle-ci, et une partie « routant » le signal audio choisi. L’aventure il y a deux ans concernait les boutons, j’avais alors mis en place une interface web pour permettre la sélection, et on avait ensuite réussi à remettre en marche les boutons. Cette fois-ci, cette semaine, on a eu des problèmes avec l’autre partie, celle concernant le signal audio.

Ça commence dimanche mais comme on communique via un carnet papier, ça passe inaperçu et lundi matin je ne découvre ces messages qu’après avoir fait deux heures de l’émission matinale dans le vide (et heureusement qu’on a des auditeur·ices pour nous faire signe).

jour qui se lève, avec le soleil rougeoyant l'horizon

16 octobre 2023, le jour se lève pendant qu’on n’est pas à l’antenne
(j’aurais mieux fait de vérifier ce qui passait à l’antenne, plutôt que prendre cette photo)

C’est pratique les congés je peux rester à la radio pour essayer de comprendre le soucis mais sans vraiment de succès. Finalement en réuploadant le programme sur l’arduino ça a remis les choses dans l’ordre, ce que je n’explique pas. Mardi matin, ça joue un tour un peu différent, le studio passe bien à l’antenne mais cinq minutes après avoir fait la bascule. Mercredi matin à nouveau autre chose, ça ne passe pas, mais en réessayant une heure plus tard ça passe.

Tout ça laisse imaginer qu’un jour ça ne marchera plus et qu’on sera bien en peine de refaire fonctionner l’affaire, je reprends donc le travail d’il y a deux ans pour attaquer la gestion du signal audio.

Le plan est simple, on pourrait brancher une deuxième carte son sur l’ordinateur de diffusion, avec 4 canaux pour y brancher les 2 studios, sur un changement de sélection on pourrait alors utiliser jack pour faire et défaire des connexions.

Pour aller au plus vite, dans l’épisode précédent il y avait création de websockets pour l’affichage sur une page web de la sélection, ça peut être réutilisé pour être notifié des changements. Ça fait une cascade assez baroque, boutons physiques → Arduino → paquet UDP → "proxy" → websocket, mais pourquoi pas.

Pour le code, l’étape 1 c’est gérer les notifications websocket, c’est très simple avec le module aiohttp,

async with aiohttp.ClientSession() as session:
    async with session.ws_connect(app_settings.SWITCH_WS_URL) as ws:
        async for msg in ws:
            if msg.type == aiohttp.WSMsgType.TEXT:
                try:
                    msg = json.loads(msg.data)
                except ValueError:
                    continue
                if msg.get('active') != currently_active:
                    currently_active = msg.get('active')
                    self.update_jack_connections(currently_active)

Pour la partie jack, dans une première version je fais avec les commandes jack_connect et jack_disconnect, en très simplifié :

def update_jack_connections(self, active):
    dports = app_settings.SWITCH_OUT_PORTS
    # ex: ('alsa_out:playback_1', 'alsa_out:playback_2')

    for port_id, port_names in app_settings.SWITCH_IN_PORTS.items():
        # ex: {
        #  0: ('netjack_soma:capture_1', 'netjack_soma:capture_2'),
        #  1: ('alsa_in:capture_1', 'alsa_in:capture_2'),
        #  2: ('alsa_in:capture_3', 'alsa_in:capture_4'),
        # }

        if port_id == active:
            cmd = 'jack_connect'
        else:
            cmd = 'jack_disconnect'
        subprocess.run([cmd, port_names[0], dports[0]])
        subprocess.run([cmd, port_names[1], dports[1]])

Mais ces commandes ne sont plus disponibles avec jack, ils ont été déplacés dans un module jack-example-tools, qui n’est pas disponible dans Debian. En réalité j’ai conservé l’ancienne version de jack parce que je trouve ces outils très pratiques mais ici je décide quand même de ne pas en dépendre, je réécris donc, en plus long :

def update_jack_connections(self, active):
    dports = app_settings.SWITCH_OUT_PORTS
    with jack.Client('switch-jack') as client:
        known_ports = {x.name for x in client.get_ports(is_audio=True)}
        for port_id, port_names in app_settings.SWITCH_IN_PORTS.items():
            if port_id == active:
                self.jack_connect(client, port_names[0], dports[0])
                self.jack_connect(client, port_names[1], dports[1])
            else:
                self.jack_disconnect(client, port_names[0], dports[0])
                self.jack_disconnect(client, port_names[1], dports[1])

def jack_connect(self, client, in_port, out_port):
    connections = [x.name for x in client.get_all_connections(in_port)]
    if out_port not in connections:
        client.connect(in_port, out_port)

def jack_disconnect(self, client, in_port, out_port):
    connections = [x.name for x in client.get_all_connections(in_port)]
    if out_port in connections:
        client.disconnect(in_port, out_port)

(la version réelle a du logging et de la gestion d’erreur en plus).

Au milieu de ces épisodes de code, on sort une carte son de l’étagère, on y branche les studios, on branche la carte son à l’ordi, on fait la liaison entre celle-ci et le jack qui tournait déjà sur la carte son existante, alsa_in -d hw:CARD=US4x4,DEV=0 -c 4 et alsa_out -d hw:CARD=US4x4,DEV=0 -c 4. (alsa_in et alsa_out sont aussi désormais dans le module jack-example-tools).

On ne branche pas la sortie de la carte son en réel vers l’antenne, on décide de garder ça en test pour le moment. (surtout que la carte son qu’on utilise nous a déjà joué des tours, en perdant le signal après quelques jours, on passera sans doute par un autre modèle).

Ça tourne, et avec les logs je peux vérifier ce matin que ça a fonctionné :

2023-10-19 20:47:45,006 (I) setting source: 1
2023-10-19 20:47:45,008 (I) disconnecting netjack_soma:capture_1 and alsa_out:playback_1
2023-10-19 20:47:45,009 (I) disconnecting netjack_soma:capture_2 and alsa_out:playback_2
2023-10-19 20:47:45,009 (I) connecting alsa_in:capture_1 and alsa_out:playback_1
2023-10-19 20:47:45,010 (I) connecting alsa_in:capture_2 and alsa_out:playback_2
2023-10-19 22:43:01,594 (I) setting source: 0
2023-10-19 22:43:01,596 (I) connecting netjack_soma:capture_1 and alsa_out:playback_1
2023-10-19 22:43:01,596 (I) connecting netjack_soma:capture_2 and alsa_out:playback_2
2023-10-19 22:43:01,597 (I) disconnecting alsa_in:capture_1 and alsa_out:playback_1
2023-10-19 22:43:01,597 (I) disconnecting alsa_in:capture_2 and alsa_out:playback_2
2023-10-20 06:59:19,629 (I) setting source: 2
2023-10-20 06:59:19,652 (I) disconnecting netjack_soma:capture_1 and alsa_out:playback_1
2023-10-20 06:59:19,653 (I) disconnecting netjack_soma:capture_2 and alsa_out:playback_2
2023-10-20 06:59:19,653 (I) connecting alsa_in:capture_3 and alsa_out:playback_1
2023-10-20 06:59:19,654 (I) connecting alsa_in:capture_4 and alsa_out:playback_2
2023-10-20 09:02:35,139 (I) setting source: 0
2023-10-20 09:02:35,141 (I) connecting netjack_soma:capture_1 and alsa_out:playback_1
2023-10-20 09:02:35,142 (I) connecting netjack_soma:capture_2 and alsa_out:playback_2
2023-10-20 09:02:35,143 (I) disconnecting alsa_in:capture_3 and alsa_out:playback_1
2023-10-20 09:02:35,143 (I) disconnecting alsa_in:capture_4 and alsa_out:playback_2

Il manque encore des bouts, en pratique quand ça va démarrer la websocket ne sera pas encore disponible, et si jamais il y a une interruption il n’y a rien pour reprendre, j’ajoute ces parties ce matin, et me voilà enfin avec quelque chose qui me semble pouvoir tenir la route. (code dans le dépôt). Et ça fait un dispositif qui devient indépendant de l’Arduino, qu’il sera possible de plus facilement mettre en place dans d’autres radios.