- THÉRÉMINE ULTRASONIQUE
- Arduino Uno + deux transducteurs HC-SR04
- Jean-Pierre Broillet septembre 2026
- Étude, câblage, calibration et programme C++ documenté
- Projet expérimental d’un instrument électronique sans contact

1. Objet du projet #
Le but est de réaliser un thérémine numérique avec un Arduino Uno et deux capteurs ultrasonores HC-SR04. Un capteur mesure la position d’une main pour commander la hauteur du son (pitch), tandis que le second commande le volume. Contrairement au thérémine historique, qui exploite la variation de capacité créée par les mains à proximité d’antennes radiofréquences, cette réalisation mesure directement des distances.
C’est aussi un instrument difficile à maîtriser, car le musicien ne dispose d’aucun repère physique pour placer ses notes.
L’intérêt pédagogique est important : acquisition ultrasonore, filtrage numérique, conversion d’une grandeur physique en paramètre musical, temporisation, génération audio et amplification.
Une virtuose du thérémine :
2. Architecture générale #
graph TD
MG@{ shape: text, label: "MAIN GAUCHE" } --> P@{ shape: text, label: "volume" } --> HCV[HC-SR04 VOLUME] --> A[Arduino UNO]
MD@{ shape: text, label: "MAIN DROITE" } --> V@{ shape: text, label: "hauteur / fréquence" } --> HCP[HC-SR04 PITCH] --> A
A --> AMP@{ shape: text, label: "Amplificateur" } --> HP@{ shape: text, label: "HP" }Les deux capteurs doivent être suffisamment séparés et, si possible, orientés selon des axes différents. Cela diminue les risques qu’un HC-SR04 reçoive l’écho de l’impulsion émise par l’autre.
3. Composants proposés #
| Quantité | Composant | Remarques |
|---|---|---|
| 1 | Arduino Uno | |
| 2 | HC-SR04 | |
| 1 | Petit amplificateur audio | LM386 ou module équivalent recommandé |
| 1 | Haut-parleur 8 Ω | |
| 1 | Bouton-poussoir CALIBRATION | Évolution recommandée |
| 1 | Bouton-poussoir MUTE | Évolution recommandée |
| — | Résistances, condensateurs de découplage, plaque d’essai et alimentation 5 V adaptée |
4. Affectation des broches #
| Fonction | Arduino Uno |
|---|---|
| TRIG HC-SR04 Pitch | D7 |
| ECHO HC-SR04 Pitch | D8 |
| TRIG HC-SR04 Volume | D9 |
| ECHO HC-SR04 Volume | D10 |
| Sortie audio | D3 |
| Alimentation HC-SR04 | +5 V |
| Masse | GND |
5. Schéma électrique de base #
Important : ne pas brancher directement un haut-parleur de 8 Ω sur une sortie de l’Arduino. La broche ne peut pas fournir le courant nécessaire. Pour les premiers essais, un étage transistor peut suffire, mais un LM386 ou un petit amplificateur audio est préférable.
6. Séquencement des mesures ultrasonores #
Les deux HC-SR04 ne doivent pas être déclenchés simultanément. Une mesure Pitch est effectuée, puis une temporisation est respectée avant la mesure Volume. Un capteur pourrait recevoir l’écho de l’impulsion émise par l’autre et donner une distance totalement fausse.
Une attente d’une trentaine de millisecondes constituent un bon point de départ pour le prototype.
graph TD
P[Mesure PITCH] --> A1[Attente]
A1 --> V[Mesure VOLUME]
V --> A2[Attente]
A2 --> T[Traitement]
T --> C[Nouveau cycle]
7. Conversion de la distance en hauteur musicale #
C’est ici que le projet devient intéressant.
Une conversion simplement linéaire, par exemple :
$$ f = ad + b $$
fonctionnerait, mais serait musicalement médiocre, parce que notre perception des hauteurs est logarithmique.
Il vaut mieux travailler par octaves :
$$ f(d) = f_{\min} 2^{Nx} $$
avec
$$ x = \frac{d_{\max} - d}{d_{\max} - d_{\min}} $$
et N le nombre d’octaves.
$$ f_{\min} = 110\ \mathrm{Hz} $$
et quatre octaves donnent :
$$ 110 \rightarrow 220 \rightarrow 440 \rightarrow 880 \rightarrow 1760\ \mathrm{Hz} $$
C’est beaucoup plus naturel à jouer.
8. Filtrage des HC-SR04 #
Les mesures brutes peuvent varier de quelques millimètres à quelques centimètres, ce qui devient immédiatement audible sous forme de vibrato parasite.
Je propose donc un filtre exponentiel :
$$ D_f(n) = \alpha D(n) + (1 - \alpha)D_f(n - 1) $$
avec :
$$ \alpha = 0{,}20 $$
Une petite valeur donne un instrument très stable mais plus lent ; une grande valeur donne un instrument vif mais plus nerveux.
C’est donc un paramètre que je mettrais explicitement dans le programme.
9. Commande du volume #
Pour le capteur Volume, la convention retenue est : main proche = silence, main éloignée = volume maximal. Dans la première version utilisant tone(), le niveau calculé sert surtout de seuil marche/arrêt. Une version évoluée utilisera une véritable modulation d’amplitude ou une sortie PWM filtrée.
10. Programme C++ complet – version de test #
1/*
2================================================================
3THEREMINE A ULTRASONS - Arduino UNO + 2 x HC-SR04
4HC-SR04 n°1 : hauteur (PITCH)
5HC-SR04 n°2 : volume
6================================================================
7*/
8
9#include <Arduino.h>
10#include <math.h>
11
12const byte TRIG_PITCH = 7;
13const byte ECHO_PITCH = 8;
14const byte TRIG_VOLUME = 9;
15const byte ECHO_VOLUME = 10;
16const byte AUDIO_PIN = 3;
17const float DIST_MIN = 5.0; // cm
18const float DIST_MAX = 50.0; // cm
19const float FREQ_MIN = 110.0; // Hz
20const float NB_OCTAVES = 4.0;
21const float ALPHA = 0.20;
22
23float distancePitchFiltree = 25.0;
24float distanceVolumeFiltree = 25.0;
25
26float mesurerDistance(byte trigPin, byte echoPin)
27{
28 digitalWrite(trigPin, LOW);
29 delayMicroseconds(3);
30 digitalWrite(trigPin, HIGH);
31 delayMicroseconds(10);
32 digitalWrite(trigPin, LOW);
33 // Timeout afin d’éviter un blocage si aucun écho n’est reçu.
34 unsigned long duree = pulseIn(echoPin, HIGH, 30000UL);
35 if (duree == 0)
36 return -1.0;
37 // Vitesse du son ~ 0,0343 cm/us ; division par 2 : aller-retour.
38 return duree * 0.0343 / 2.0;
39}
40
41float limiter(float valeur, float minimum, float maximum)
42{
43 if (valeur < minimum)
44 valeur = minimum;
45 if (valeur > maximum)
46 valeur = maximum;
47 return valeur;
48}
49
50float distanceVersFrequence(float distance)
51{
52 distance = limiter(distance, DIST_MIN, DIST_MAX);
53 // Main proche -> x = 1 ; main éloignée -> x = 0.
54 float x = (DIST_MAX - distance) / (DIST_MAX - DIST_MIN);
55 // Loi exponentielle correspondant à NB_OCTAVES octaves.
56 return FREQ_MIN * pow(2.0, NB_OCTAVES * x);
57}
58
59int distanceVersVolume(float distance)
60{
61 distance = limiter(distance, DIST_MIN, DIST_MAX);
62 // Main proche -> silence ; main éloignée -> maximum.
63 float x = (distance - DIST_MIN) / (DIST_MAX - DIST_MIN);
64 return constrain((int)(255.0 * x), 0, 255);
65}
66
67void setup()
68{
69 pinMode(TRIG_PITCH, OUTPUT);
70 pinMode(ECHO_PITCH, INPUT);
71 pinMode(TRIG_VOLUME, OUTPUT);
72 pinMode(ECHO_VOLUME, INPUT);
73 pinMode(AUDIO_PIN, OUTPUT);
74 Serial.begin(115200);
75 Serial.println("Theremine ultrasonique - initialisation");
76}
77
78void loop()
79{
80 // Mesure du capteur de hauteur.
81 float dPitch = mesurerDistance(TRIG_PITCH, ECHO_PITCH);
82 if (dPitch > 0)
83 distancePitchFiltree =
84 ALPHA * dPitch + (1.0 - ALPHA) * distancePitchFiltree;
85 // Séparation temporelle des émissions ultrasonores.
86 delay(30);
87 // Mesure du capteur de volume.
88 float dVolume = mesurerDistance(TRIG_VOLUME, ECHO_VOLUME);
89 if (dVolume > 0)
90 distanceVolumeFiltree =
91 ALPHA * dVolume + (1.0 - ALPHA) * distanceVolumeFiltree;
92 float frequence = distanceVersFrequence(distancePitchFiltree);
93 int volume = distanceVersVolume(distanceVolumeFiltree);
94 // Première version : tone() produit une onde carrée.
95 // Le volume est ici utilisé comme seuil d’activation.
96 if (volume > 10)
97 tone(AUDIO_PIN, (unsigned int)frequence);
98 else
99 noTone(AUDIO_PIN);
100 // Informations de mise au point sur le PC.
101 Serial.print("Pitch : ");
102 Serial.print(distancePitchFiltree, 1);
103 Serial.print(" cm F = ");
104 Serial.print(frequence, 1);
105 Serial.print(" Hz Volume : ");
106 Serial.print(distanceVolumeFiltree, 1);
107 Serial.print(" cm Niveau = ");
108 Serial.println(volume);
109 delay(10);
110}11. Mais je modifierais ensuite la génération sonore #
tone() est excellent pour vérifier le fonctionnement, mais pas pour obtenir un beau thérémine. Il produit essentiellement une onde carrée riche en harmoniques impaires.
Le projet devient beaucoup plus intéressant avec cette chaîne :
block-beta
columns 3
%% Chaîne verticale à gauche, entrée Volume à droite.
P["HC-SR04 PITCH"] space:2
space:3
F["Filtrage distance"] space:2
space:3
C["Calcul de fréquence"] space:2
space:3
O["Oscillateur numérique"] space:2
space:3
W["Forme d’onde audio"] space:2
space:3
V["Contrôle volume"] space Q["HC-SR04 VOLUME"]
space:3
FL["Filtre"] space:2
space:3
A["Amplificateur"] space:2
space:3
HP["Haut-parleur"] space:2
P --> F
F --> C
C --> O
O --> W
W --> V
Q --> V
V --> FL
FL --> A
A --> HPOn pourrait générer une onde triangulaire ou une combinaison sinusoïde + harmoniques donnant un timbre beaucoup plus agréable.
12. Le « calage » que je recommande #
C’est même un point que je considère essentiel.
Plutôt que d’imposer définitivement 5 et 50 cm dans le programme, le thérémine devrait posséder une procédure de calibration.
Au démarrage :
flowchart TD
subgraph PITCH[CALAGE PITCH]
PB[Main position BASSE] --> PM[Mesure distance]
PM --> PMin[Mémorisation DPmin]
PH[Main position HAUTE] --> PX[Mesure distance]
PX --> PMax[Mémorisation DPmax]
end
subgraph VOLUME[CALAGE VOLUME]
VS[Position silence] --> VM[Mesure DVmin]
VV[Position volume maximum] --> VX[Mesure DVmax]
end
style PITCH fill:transparent,stroke:transparent
style VOLUME fill:transparent,stroke:transparent
PITCH ~~~ VOLUMEOn peut ajouter deux boutons-poussoirs, ou mieux encore un bouton CALIBRATION et utiliser le moniteur série pour guider l’utilisateur.
Je verrais volontiers :
flowchart LR
PITCH["PITCH
HC-SR04"] --> THEREMINE
VOLUME["VOLUME
HC-SR04"] --> THEREMINE
subgraph THEREMINE[THEREMINE]
direction TB
CALIB["[ CALIB ]"]
MUTE["[ MUTE ]"]
HP(("HP"))
CALIB ~~~ MUTE
MUTE ~~~ HP
end
style THEREMINE fill:transparent,stroke:currentColor,stroke-width:1pxLe bouton MUTE serait particulièrement pratique : un thérémine sans moyen simple de couper le son devient vite pénible pendant les essais.
13. Une amélioration très importante #
Il faudrait séparer mesure, traitement et production audio.
Dans le programme précédent, pulseIn() est bloquant. Cela suffit pour une démonstration, mais ce n’est pas idéal pour un véritable instrument.
La version évoluée pourrait fonctionner ainsi :
block-beta
columns 3
space ARDUINO["ARDUINO UNO"] space
J_MESURES((" ")) J_TRAITEMENT((" ")) J_AUDIO((" "))
MESURES["MESURES
ultrason"] TRAITEMENT["TRAITEMENT
numérique"] AUDIO["AUDIO
timer"]
space space space
DISTANCE["distance"] FILTRAGE["filtrage"] OSCILLATEUR["oscillateur"]
J_DISTANCE((" ")) FREQUENCE["fréquence"] J_FREQUENCE((" "))
space VOLUME["volume"] J_VOLUME((" "))
space space PWM["sortie PWM"]
ARDUINO --- J_TRAITEMENT
J_MESURES --- J_TRAITEMENT
J_TRAITEMENT --- J_AUDIO
J_MESURES --- MESURES
J_TRAITEMENT --- TRAITEMENT
J_AUDIO --- AUDIO
MESURES --- DISTANCE
TRAITEMENT --- FILTRAGE
AUDIO --- OSCILLATEUR
DISTANCE --- J_DISTANCE
J_DISTANCE --- FREQUENCE
FILTRAGE --- FREQUENCE
FREQUENCE --- VOLUME
FREQUENCE --- J_FREQUENCE
VOLUME --- J_VOLUME
OSCILLATEUR --- J_FREQUENCE
J_FREQUENCE --- J_VOLUME
J_VOLUME --- PWM
classDef texte fill:transparent,stroke:transparent
classDef jonction fill:none,stroke:none
class ARDUINO,MESURES,TRAITEMENT,AUDIO,DISTANCE,FILTRAGE,OSCILLATEUR,FREQUENCE,VOLUME,PWM texte
class J_MESURES,J_TRAITEMENT,J_AUDIO,J_DISTANCE,J_FREQUENCE,J_VOLUME jonctionLa génération sonore par Timer1 avec accumulateur de phase serait nettement supérieure : l’audio continuerait à être généré régulièrement pendant les calculs et les acquisitions.
14. Architecture finale #
Arduino Uno + 2 HC-SR04 + bouton CALIB + bouton MUTE + sortie audio PWM + filtre RC + LM386 + haut-parleur 8 Ω.
Et surtout, je ferais évoluer le logiciel en trois étapes : d’abord la version tone() ci-dessus pour valider la géométrie et les capteurs ; ensuite une version avec vrai contrôle continu du volume ; enfin une version avec DDS/Timer, forme d’onde travaillée et calibration automatique.
Cette dernière version serait un véritable petit instrument numérique et non plus seulement une démonstration HC-SR04. Elle permettrait même d’ajouter ultérieurement vibrato, choix du timbre, changement du nombre d’octaves et quantification facultative sur les notes de la gamme.
L’accumulateur de phase #
L’accumulateur de phase est une technique numérique très élégante pour fabriquer un signal périodique de fréquence précise. Dans notre thérémine, il permettrait de remplacer avantageusement tone().
Le principe #
Imaginez un tour complet de cercle correspondant à une période du signal :
$$ 0^\circ \rightarrow 90^\circ \rightarrow 180^\circ \rightarrow 270^\circ \rightarrow 360^\circ $$
À chaque « tic » d’une horloge très régulière, on avance d’une certaine quantité sur ce cercle. Cette position est la phase.
On utilise donc une variable entière :
$$ \mathrm{phase}_{n+1} = \mathrm{phase}_n + \mathrm{increment} $$
C’est cette variable phase que l’on appelle accumulateur de phase.
Plus increment est grand, plus on fait rapidement le tour, donc plus la fréquence sonore est élevée.
Exemple très simple #
Supposons arbitrairement un accumulateur allant de 0 à 255 #
Avec un incrément de 1 :
$$ 0 \rightarrow 1 \rightarrow 2 \rightarrow 3 \rightarrow 4 \rightarrow \cdots \rightarrow 255 \rightarrow 0 \rightarrow 1 \rightarrow \cdots $$
Il faut 256 coups d’horloge pour effectuer une période.
Avec un incrément de 4 :
$$ 0 \rightarrow 4 \rightarrow 8 \rightarrow 12 \rightarrow \cdots \rightarrow 252 \rightarrow 0 \rightarrow \cdots $$
Il ne faut plus que :
$$ \frac{256}{4} = 64 $$
coups d’horloge pour une période.
La fréquence est donc quatre fois plus élevée.
Relation mathématique #
Avec un accumulateur de phase de N bits possède :
$$ 2^N $$
états.
Si la fréquence d’échantillonnage est Fs et l’incrément de phase vaut K, la fréquence générée est :
$$ F_{\mathrm{son}} = \frac{K F_s}{2^N} $$
Prenons par exemple un accumulateur 32 bits et une interruption audio à :
$$ F_s = 31250\ \mathrm{Hz} $$
Pour produire le La à 440 Hz :
$$ K = \frac{440 \times 2^{32}}{31250} $$
ce qui donne environ :
$$ K = 60\ 472\ 886 $$
Cela paraît énorme, mais pour un microcontrôleur ce n’est qu’un entier 32 bits.
Pour quelle raison, c’est mieux que tone() ?
#
tone() est pratique pour un prototype, mais l’accumulateur de phase permet de contrôler indépendamment fréquence, amplitude et forme d’onde. On peut ainsi produire une sinusoïde, un triangle, un signal enrichi en harmoniques, ajouter du vibrato et faire varier la fréquence de façon parfaitement continue.
Et il y a une propriété particulièrement intéressante pour le thérémine : lorsque la fréquence change, la phase continue. On ne redémarre pas la sinusoïde à zéro à chaque nouvelle mesure de la main. Les glissandos deviennent donc naturellement continus.
C’est précisément pour cette raison que je propose cette technique. Je suis d’avis que la prochaine étape intéressante est de construire une vraie version DDS + Timer, en expliquant en détail Timer1, fréquence d’échantillonnage, accumulateur 32 bits, table sinus et PWM, puis d’en tirer le programme C++ complet du thérémine.