Skip to content

Décodeur DTMF

Quand une touche de téléphone est pressée, l'appareil émet la somme de deux sinusoïdes de fréquences connues, une par ligne du clavier, une par colonne : c'est le code DTMF (dual-tone multi-frequency), toujours utilisé par les serveurs vocaux. Décoder un numéro composé, c'est répondre à deux questions en boucle : quand une tonalité est-elle présente, et laquelle des seize touches a produit chaque tonalité. La première est un problème de détection à seuil, la seconde une décision multi-hypothèses par banque de filtres adaptés : ce projet fait construire le décodeur complet, du signal audio au numéro de téléphone.

1209 Hz1336 Hz1477 Hz1633 Hz
697 Hz123A
770 Hz456B
852 Hz789C
941 Hz*0#D

1. Lien avec le cours

Le projet est le capstone du chapitre 3 : un détecteur à seuil dont le seuil est calibré pour un budget PFA, le filtre adapté décliné en banque de corrélateurs, et la matrice de confusion pour juger la décision à seize hypothèses. Il réutilise du chapitre 2 la base de Fourier et la matrice de Gram, qui expliquera le compromis de fenêtrage.

2. Structure du code

Le modèle est une chaîne de transmission en trois classes : l'encodeur et le canal fabriquent le monde physique, le décodeur est l'endroit exact où les algorithmes du cours s'insèrent, une méthode par algorithme.

python
class DTMFEncoder:
    """Keys to waveform: two known frequencies per key, tones and silences."""

    def __init__(self, fs=8000, tone=0.08, silence=0.08, amplitude=1.0):
        ...

    def encode(self, keys):
        """'0612...' -> waveform s[n]."""
        ...


class AWGNChannel:
    """Additive white Gaussian noise channel."""

    def __init__(self, sigma):
        self.sigma = sigma

    def transmit(self, s, rng):
        """x = s + w, with w ~ N(0, sigma^2)."""
        ...


class DTMFDecoder:
    """Waveform to keys: every course algorithm lives in one method."""

    def __init__(self, fs=8000, window=0.04, gamma=None):
        ...

    def segment(self, x):
        """Sliding-window energy detector: tone-present intervals."""
        ...

    def correlate(self, frame):
        """Correlator bank on the 8 known frequencies (both quadratures)."""
        ...

    def decode(self, x):
        """Full chain: segment, correlate, decide -> '0612...'."""
        ...

Tests imposés. L'aller-retour sans bruit, decode(encode(keys)) == keys, sur les seize touches ; la PFA empirique du segmenteur sur du bruit seul, qui doit retomber sur le budget choisi à quatre écarts-types près ; et la géométrie des gabarits, quasi orthogonaux sur une fenêtre de 40 ms, fortement corrélés entre 697 et 770 Hz sur une fenêtre de 5 ms.

3. Travail demandé

  1. L'encodeur. Implémenter DTMFEncoder.encode : pour chaque touche, la somme des deux sinusoïdes de la table, suivie d'un silence. Écouter le résultat (IPython.display.Audio) : un numéro composé doit sonner comme un téléphone.
  2. Le canal. Implémenter AWGNChannel.transmit et définir proprement le SNR utilisé dans tout le projet, le rapport entre la puissance d'une tonalité et σ2, en dB. Écouter le numéro à 10, 0 et 5 dB.
  3. La segmentation. Implémenter segment : l'énergie T=x[n]2 sur une fenêtre glissante, comparée à un seuil γ. Calibrer γ sous H0 : simuler du bruit seul, prendre le quantile empirique correspondant à PFA=1% par fenêtre, puis vérifier cette calibration sur de nouveaux tirages. Fusionner les fenêtres contiguës en intervalles de tonalité, en imposant une durée minimale.
  4. La banque de corrélateurs. Implémenter correlate : pour un segment donné, corréler avec les gabarits cos et sin de chacune des 8 fréquences, et sommer les deux carrés, la phase à l'entrée d'un segment étant inconnue, exactement la séparation des variables linéaires du projet d'analyse spectrale. Décider la touche par la ligne et la colonne les plus fortes.
  5. La performance. Estimer par Monte-Carlo la matrice de confusion 16×16 à SNR fixé, puis l'exactitude en fonction du SNR de 15 à +10 dB. Où sont les confusions : entre touches d'une même ligne, d'une même colonne, au hasard ?
  6. Le compromis de fenêtrage. Refaire l'étape 5 à SNR fixé pour des fenêtres de 5, 10, 20 et 40 ms. Expliquer les deux régimes avec les outils du cours : fenêtre courte, les colonnes cos/sin des fréquences voisines deviennent quasi colinéaires et la matrice de Gram se dégrade, les lignes voisines se confondent ; fenêtre longue, la résolution est excellente mais les tonalités courtes ou rapprochées ne sont plus séparées par le segmenteur. Proposer et justifier une longueur de fenêtre.
  7. Bout en bout. Décoder un numéro complet de dix chiffres à plusieurs SNR : à partir de quel SNR le numéro passe-t-il sans erreur ? Bonus : enregistrer un vrai clavier DTMF (une application de numérotation) avec un microphone, et décoder l'enregistrement.

4. Livrables

Le notebook reproductible des modalités communes, avec les écoutes aux étapes 1 et 2, la vérification de calibration de l'étape 3, la matrice de confusion et la courbe exactitude/SNR, l'étude de fenêtrage commentée, et le verdict du décodage bout en bout.