Le week-end du CQ WW RTTY, les bandes sont pleines de RTTY du vendredi soir au dimanche minuit. C’était l’occasion que j’attendais. Mon Flex-6500 tourne sous Linux avec AetherSDR, qui expose un serveur TCI, et je n’avais aucun moyen satisfaisant d’y brancher un décodeur RTTY. L’excellent rttyTCI de DL1BZ ne tourne que sous macOS et n’est pas ouvert. fldigi demande de bricoler un routage audio virtuel. Quant au décodeur RTTY intégré à AetherSDR, il restait muet dès que les signaux n’étaient plus très forts.
J’ai donc écrit le mien. Il s’appelle Emile-RTTY, en hommage à Émile Baudot, dont le code télégraphique à cinq éléments (1874) est l’ancêtre de l’ITA2 que nous utilisons toujours en RTTY. Cet article raconte la démarche, du premier enregistrement audio jusqu’à un outil de contest complet, avec ce qui a marché du premier coup et ce qui a demandé plusieurs essais.
Écouter avant de décoder
Le TCI (Transceiver Control Interface) est un protocole créé par Expert Electronics pour ExpertSDR, et repris depuis par d’autres logiciels SDR. Tout passe par une seule connexion WebSocket : les commandes sont du texte terminé par un point-virgule, et l’audio arrive en trames binaires précédées d’un en-tête de 64 octets (seize entiers de 32 bits : numéro de récepteur, fréquence d’échantillonnage, format, nombre d’échantillons, type de trame, nombre de canaux…). Pour recevoir l’audio du récepteur 1 en float32 mono à 48 kHz, il suffit d’envoyer :
for cmd in ("audio_samplerate:48000;", "audio_stream_sample_type:float32;",
"audio_stream_channels:1;", "audio_stream_samples:512;",
"audio_start:0;"):
await ws.send(cmd)Plutôt que d’écrire tout de suite un décodeur temps réel, j’ai commencé par un petit script, tci_record.py, qui enregistre ce flux dans un fichier WAV et cherche dans le spectre les paires de raies espacées de 170 Hz. Pendant le contest, j’ai ainsi constitué une série d’enregistrements réels sur 20 m : signaux forts, signaux faibles, pile-up. Ces fichiers ont été la clé de tout le reste. On peut rejouer cent fois le même passage difficile et mesurer objectivement si une modification améliore le décodage ou non. Au passage, ils confirment qu’en mode RTTY du Flex, le mark est le ton bas, vers 2125 Hz audio.
Le démodulateur, étape par étape
Le RTTY amateur, c’est 45,45 bauds, un shift de 170 Hz et le code Baudot ITA2 : chaque caractère est un bit de start, cinq bits de données et un bit et demi de stop. La démodulation retenue est classique et non cohérente : chaque ton (mark et space) est ramené à 0 Hz, filtré, puis on compare les deux enveloppes. Les caractères sont ensuite reconnus comme sur un port série : on guette le front du bit de start, puis on juge chaque bit sur la moitié centrale de sa durée.
Un filtre adapté au débit
La première version utilisait un passe-bas ordinaire. Le remplacer par un filtre adapté au débit, une fenêtre en cosinus surélevé couvrant 1,4 durée de bit, a fait gagner environ 3 dB sur signaux de test. C’est la différence entre un texte haché et un texte lisible quand le signal est à peine au-dessus du bruit.
L’ATC contre le fading sélectif
En HF, il arrive souvent qu’un des deux tons s’affaiblisse pendant que l’autre reste fort. Une simple comparaison mark contre space se trompe alors systématiquement. J’ai repris la correction automatique de seuil « optimale » de Kok Chen, W7AY, également utilisée dans fldigi : chaque ton a son propre suivi de crête, le plancher de bruit est suivi à part, et chaque ton est jugé par rapport à son propre niveau récent.
xa = min(max(va, bruit), crete_a) - bruit # ton bas, écrêté
xb = min(max(vb, bruit), crete_b) - bruit # ton haut, écrêté
pa, pb = crete_a - bruit, crete_b - bruit
d = xa * pa - xb * pb - 0.25 * (pa * pa - pb * pb) # > 0 : markSur mes enregistrements affectés de fading sélectif, cela a supprimé environ un tiers des erreurs.
Faire taire le bruit : squelch et filtre « rafale »
Un décodeur RTTY laissé seul sur du bruit invente des caractères en permanence. Deux protections se complètent. Le squelch mesure la netteté de chaque caractère : sur un vrai signal, un ton domine franchement ; sur du bruit, non. Le filtre « rafale » s’appuie sur un constat simple : un vrai texte arrive en continu, un caractère toutes les 165 ms environ, alors que le bruit qui passe le squelch donne des caractères isolés. Il faut donc au moins trois caractères à la suite pour que l’affichage commence. Les parasites isolés disparaissent, le texte réel passe intact.
L’AFC, taillée pour les pile-up
Les enregistrements ont apporté une mesure intéressante : une station ne dérive quasiment pas pendant son émission (±3 Hz), mais en pile-up les appelants arrivent décalés jusqu’à ±50 Hz les uns des autres. Or le décodeur tolère environ 20 Hz d’erreur et décroche vers 50 Hz. L’AFC cherche donc la paire mark/space la plus forte dans une fenêtre de ±60 Hz autour de la fréquence choisie par l’opérateur, quatre fois par seconde. Elle ne se recale que si l’écart dépasse 12 Hz et si le signal sort nettement du bruit. La fenêtre reste ancrée sur la consigne : l’AFC ne peut pas partir à la dérive vers la station voisine.
Ce que ça donne sur l’air
Pendant le contest, Emile-RTTY a décodé entre autres 9A3SMS, OH5Z et son pile-up, CR3DX et ses appelants (LA7VRA, W6SX, N0OJ, K3WHD…), YT3D, VK5CS, KA1C et US5CDH. Au-dessus de S9, le décodage est quasi parfait. Entre S3 et S5, il se dégrade, et c’est normal : ce qui compte n’est pas l’aiguille du S-mètre mais l’écart entre le signal et le bruit, qui tournait autour de S3-S4 sur 20 m pendant le contest. Sur un enregistrement S5 difficile, le résultat est à égalité avec minimodem, un peu plus propre même, alors que le décodeur intégré d’AetherSDR n’affichait rien.
Passer en émission
Le TCI sait aussi émettre de l’audio. On passe en émission avec trx:0,true,tci;, puis le serveur réclame l’audio au rythme exact dont il a besoin : il envoie des trames TX_CHRONO indiquant combien d’échantillons il attend, et le programme répond par autant d’échantillons dans une trame TX_AUDIO. Le signal AFSK est généré avec une phase continue, des transitions mark/space lissées sur 4 ms et des rampes d’amplitude de 10 ms, pour un signal propre et étroit, sans clics. Côté Baudot, deux codes LTRS ouvrent chaque émission pour synchroniser le correspondant. Comme presque tous les logiciels repassent en lettres après un espace (UOS), un chiffre qui suit un espace est toujours précédé d’un code FIGS.
Le premier essai s’est fait sur charge fictive : le Flex accepte bien l’audio TCI en mode RTTY. Le premier vrai QSO a suivi dans la foulée, pendant le CQ WW RTTY, le 26 septembre 2026, avec CR3DX à Madère, échange 599 14 14. Un premier QSO avec un logiciel qu’on a écrit soi-même, ça reste un vrai plaisir.
Un vrai outil de contest
Décoder et émettre ne suffit pas pour trafiquer confortablement. Emile-RTTY a donc grandi pour couvrir le déroulement complet d’un QSO.
- Deux jeux de macros, « Contest » et « QSO », chacun organisé en RUN et S&P et accessibles par les touches F1 à F11. Les variables {MOI}, {DX}, {ECH}, {RST}, {NOM} et {QTH} sont remplacées à l’émission. Un clic droit sur une macro permet de la modifier, et tout est sauvegardé dans un fichier de configuration.
- Un journal ADIF choisi au lancement : un fichier par concours et un pour le trafic courant, importable dans n’importe quel logbook. Un double-clic sur un indicatif dans le texte reçu le place dans la case du correspondant.
- L’export Cabrillo, le format exigé par les organisateurs de concours, avec son en-tête mémorisé d’un concours à l’autre.
- Le fichier pays cty.dat d’AD1C (country-files.com) : pays, zone CQ et continent du correspondant, zone reçue préremplie, et signalement des doublons et des nouveaux multiplicateurs par bande.
Une précaution sur la zone préremplie : elle ne remplace pas ce qu’on a réellement copié. Le règlement des concours CQ interdit de corriger indicatifs et échanges après coup à l’aide d’une base de données, et un échange mal reçu fait perdre le QSO. Si la station envoie autre chose que ce que propose le logiciel, on corrige avant de loguer.
Un coup de jeune
La première interface était fonctionnelle, sans plus. Avant de la publier, je lui ai donné l’allure d’une console moderne, aux couleurs de hamanalyst.org : fond nuit, ambre pour ce qui est à moi ou actif (mon texte émis, le mark, les options allumées), cyan pour l’information (le space, le pays du correspondant, les QSO enregistrés). Le spectre a laissé la place à une cascade, bien plus parlante pour repérer les appelants décalés d’un pile-up. Une carte « QSO en cours » rassemble d’un coup d’œil l’indicatif, le pays, la zone et les badges doublon ou multiplicateur. Les macros sont devenues des tuiles.

Même cette étape cosmétique a réservé une surprise. La première cascade était saturée : tout le bloc autour du signal apparaissait en jaune vif. En cause, le filtre RTTY du Flex, étroit, qui crée une marche de plus de 60 dB entre sa bande passante et le reste du spectre. Le réglage automatique de contraste prenait le silence hors du filtre comme référence. La solution a été de mesurer le bruit dans le filtre quand il est étroit. Les deux tons ressortent maintenant nettement, avec la manipulation visible en marches.
Comment ce programme a été construit
Je le dis franchement : Emile-RTTY a été développé en dialogue avec une intelligence artificielle, Claude d’Anthropic. La répartition des rôles a été claire. De mon côté : la station, les enregistrements, les essais sur l’air, les besoins d’opérateur et les choix. Du côté de l’IA : l’écriture du code, les mesures sur mes fichiers et les propositions techniques, que je validais ou non sur l’air.
Ce qui a fait la différence, c’est la méthode plus que l’outil. On a d’abord enregistré, ensuite mis au point hors ligne sur de vrais signaux, et seulement après branché le temps réel. Chaque amélioration du démodulateur a été mesurée sur les mêmes fichiers avant d’être gardée. Plusieurs étapes ont demandé des allers-retours : le choix du filtre, l’ATC, le comportement de l’AFC en pile-up, la cascade. Une IA ne remplace ni l’écoute ni l’expérience de trafic. Bien guidée et confrontée à des mesures réelles, elle permet en revanche d’aller au bout d’un projet qu’on n’aurait peut-être jamais commencé seul.
Installation
Emile-RTTY est un seul fichier Python. Il fonctionne avec tout logiciel SDR qui propose un serveur TCI (testé avec AetherSDR et un Flex-6500). Sur une distribution Debian ou Ubuntu :
sudo apt install python3-tk python3-numpy python3-websockets git
git clone https://github.com/albertM-hub/Emile-RTTY.git
cd Emile-RTTY
python3 emile_rtty.pyAu premier lancement, une fenêtre « Station » demande votre indicatif, votre prénom, votre QTH et votre échange de contest : tant que l’indicatif n’est pas réglé, l’émission est refusée. Le programme demande ensuite quel journal ADIF utiliser. Pour les informations pays, déposez le cty.dat d’AD1C dans ~/.config/emile-rtty/, ou laissez le programme utiliser celui de WSJT-X s’il est installé. Pour vos premiers essais en émission, commencez à faible puissance et surveillez l’ALC.
Le code source, la documentation et les outils de mise au point sont sur GitHub, sous licence libre GNU GPL v3 : github.com/albertM-hub/Emile-RTTY
Le code est libre : n’hésitez pas à l’essayer, à me faire part de vos résultats avec d’autres transceivers ou logiciels TCI, et à proposer vos améliorations. Rendez-vous en RTTY au prochain concours.
73 de ON5AM, Albert
Licencié Harec depuis 1990, après une pause de quelques années, j'ai renouvelé mon intérêt pour la radio, je suis particulièrement actif en HF, appréciant le FT8, les contest et la chasse au Dx. Passionné d'informatique, je suis convaincu que le monde des radioamateurs doit évoluer avec les avancées technologiques, notamment avec l'émergence de l'IA dans nos shack.







