Laboratoire 6
=============

Dans ce laboratoire, nous allons étudier des phénomènes ondulatoires,
en simulant le comportement d'une corde élastique soumise à des
perturbations.

Modélisation
------------

On souhaite simuler le mouvement d'une corde élastique tendue entre
deux points, dans un espace à deux dimensions. Pour modéliser ce système,
nous allons considérer que cette corde est constituée
d'un certain nombre :math:`N + 1` de petites masses ponctuelles
reliées par des ressorts. (Plus la valeur de :math:`N` sera grande,
plus la simulation sera précise.) La figure suivante illustre cette
modélisation:

.. figure:: figures/string.png
   :scale: 100%
   :align: center
   :alt: Modélisation de la corde.

   Modélisation de la corde.

Principes:

- Notons :math:`p_i = (x_i, y_i)` la position dans le plan de chaque
  point, pour :math:`i = 0, 1, \ldots, N`. Entre deux points successifs,
  la distance horizontale :math:`\Delta` est identique; on a donc
  :math:`x_i = i \Delta`.

- Pour :math:`i = 1, 2, \ldots, N-1` (le cas des extrémités de la corde
  est particulier et sera traité plus tard), les forces agissant sur
  le point :math:`i` sont:

  * l'attraction :math:`\vec{f_{i,G}} = k\, \overrightarrow{p_i p_{i-1}}`
    du ressort situé à sa gauche, et
  * l'attraction :math:`\vec{f_{i,D}} = k\, \overrightarrow{p_i p_{i+1}}`
    du ressort situé à sa droite,

  où :math:`k` est la *raideur* de chaque ressort.

  *Notes:*

  * On ne représente pas l'effet exercé par la gravité sur
    la corde. 
  * On ajoutera plus tard à ce modèle un mécanisme d'amortissement
    afin de le rendre plus réaliste.

- La résultante des forces agissant sur le point :math:`p_i = (x_i, y_i)`,
  pour  :math:`i = 1, 2, \ldots, N-1`, vaut donc:

  * :math:`f_{i,X} = -k \Delta + k \Delta = 0` selon l'axe horizontal.
  * :math:`f_{i,Y} = k(y_{i-1} - y_i) + k(y_{i+1} - y_i) = k(y_{i-1} + y_{i+1} -2y_i)`
    selon l'axe vertical.

  La conséquence du premier résultat est que la position horizontale
  :math:`x_i` de :math:`p_i` reste constante.  Pour déterminer comment
  sa position verticale :math:`y_i` évolue avec le temps, on peut
  appliquer la deuxième loi de Newton:

    :math:`f_{i, Y} = m \ddot{y}_i`,

  où

  * :math:`m` est la masse de :math:`p_i`,
  * :math:`\ddot{y}_i` est l'accélération subie par :math:`p_i` selon
    l'axe vertical, en d'autres termes
    :math:`\displaystyle\ddot{y}_i = \frac{\partial^2y_i}{\partial t^2}`.

    *Note:* Dans l'expression précédente, on utilise le symbole de dérivée
    partielle :math:`\partial` car :math:`y_i` peut être vue comme une fonction
    des deux variables suivantes:

    - La position horizontale :math:`x` sur la corde. (La dérivée de :math:`y_i`
      par rapport à cette variable correspond à la pente de la corde au point
      :math:`p_i`.)
	    
    - Le temps :math:`t`. (La derivée de :math:`y_i`
      par rapport à cette variable correspond à la vitesse verticale du
      point :math:`p_i`.)

  On obtient donc:

    :math:`\displaystyle\ddot{y}_i = \frac{k}{m}(y_{i-1} + y_{i+1} -2y_i)~~~(1)`
    
- En pratique, les valeurs de :math:`k` et de :math:`m` ne sont pas
  directement connues, car elles dépendent du nombre :math:`N` choisi
  pour discrétiser la corde, qui n'a pas d'existence physique. Il est
  plus commode d'exprimer la valeur de ces deux variables en fonction de:

  * la force de tension :math:`T` appliquée à la corde. Etant donné
    que, lorsque la corde est à l'équilibre, cette force est exercée par
    chaque ressort lorsqu'il est étiré à la longueur :math:`\Delta`, on a
    par la loi de Hooke :math:`T = k\Delta`,
    qui donne:

      :math:`\displaystyle k = \frac{T}{\Delta}~~~~~(2)`

  * la densité linéaire :math:`\mu` de la corde, c'est-à-dire sa masse
    par unité de longueur (exprimée en kilogrammes par mètre). La
    masse :math:`m` d'un segment de corde de longueur :math:`\Delta`
    vaut alors:

      :math:`m = \mu \Delta~~~(3)`

  En introduisant :math:`(2)` et :math:`(3)` dans :math:`(1)`, on obtient:

    :math:`\displaystyle\ddot{y}_i = \frac{T}{\mu\Delta^2}(y_{i-1} + y_{i+1} -2y_i)~~~(4)`

- Dans l'expression précédente, le terme

    :math:`\displaystyle\frac{y_{i-1} + y_{i+1} -2y_i}{\Delta^2}`

  approxime la dérivée seconde :math:`\displaystyle\frac{\partial^2 y}{\partial x^2}`
  de :math:`y` par rapport à :math:`x`. En effet, la derivée de
  :math:`y` par rapport à :math:`x` est approximée par

  * :math:`\displaystyle\frac{y_i - y_{i-1}}{\Delta}` en :math:`x = x_i`,
  * :math:`\displaystyle\frac{y_{i+1} - y_{i}}{\Delta}` en :math:`x = x_{i+1}`.

  La dérivée de cette dérivée vaut alors:

    :math:`\displaystyle \frac{\partial^2 y}{\partial x^2} \approx \frac{(y_{i+1} - y_{i}) - (y_i - y_{i-1})}{\Delta^2} = \frac{y_{i-1} + y_{i+1} -2y_i}{\Delta^2}~~~(5)`.

  En remplaçant :math:`(5)` dans :math:`(4)`, on obtient:

    :math:`\displaystyle\frac{\partial^2y_i}{\partial t^2} = \frac{T}{\mu}  \frac{\partial^2 y}{\partial x^2}~~~(6)`

- L'équation différentielle :math:`(6)` est un cas particulier de l'équation

    :math:`\displaystyle\frac{\partial^2y_i}{\partial t^2} = v^2 \frac{\partial^2 y}{\partial x^2}`

  appelée **équation d'onde**. On peut montrer que la solution
  générale de cette équation prend la forme de deux signaux quelconques
  se propageant respectivement vers la gauche et vers la droite, tous
  deux à la vitesse :math:`v`. (Pour plus d'informations, voir par exemple
  `cette référence <http://mathworld.wolfram.com/WaveEquation1-Dimensional.html>`_ ou `celle-ci <http://mathworld.wolfram.com/dAlembertsSolution.html>`_.)

  Dans notre cas, nous nous attendons donc à observer des signaux se
  propageant sur la corde à la vitesse
  :math:`\displaystyle v = \sqrt{\frac{T}{\mu}}`. Dans la section suivante,
  vous allez développer un outil de simulation permettant de réaliser cette
  expérience.

Outil de simulation
-------------------

L'objectif consiste à construire un programme qui simule en temps réel
le mouvement de la corde. Pour ce faire, vous allez fixer la valeur
de :math:`N`, par exemple à :math:`N = 100`, et appliquer répétitivement
l'équation :math:`(4)` pour calculer le mouvement de chaque point.

Avant de commencer, il reste un détail à préciser. Dans la section
précédente, nous n'avons pas abordé le calcul du mouvement des points
situés aux extrémités de la corde, c'est-à-dire :math:`p_0 = (0, y_0)`
et :math:`p_N = (N \Delta, y_N)`. Plusieurs choix sont possibles; par
exemple, forcer :math:`y_0 = y_N = 0` revient à modéliser une
corde tendue entre deux points fixes. Dans un premier temps, afin
de pouvoir facilement observer des comportements intéressants, notre
choix sera légèrement différent:

- La position verticale :math:`y_0` de l'extrémité gauche de la corde
  sera déterminée sur base des mouvements de la souris, afin de disposer
  d'un mécanisme simple pour exciter la corde.

- A l'extrémité droite de la corde, on aura :math:`y_N = 0`, représentant
  une corde fixée à un objet immobile.

Procédure à suivre:

1) Ecrire un programme Python basé sur Pygame qui ouvre une fenêtre
   et la peint dans une couleur de fond de votre choix. Vous pouvez
   par exemple utiliser comme point de départ le programme donné à la
   fin du :doc:`guide rapide de Python et Pygame </guide-python>`, que
   vous pouvez recopier dans un nouveau fichier ``prog-15.py``.

2) La boucle principale de ce programme n'est pas adaptée à une simulation
   en temps réel. En effet, cette boucle contient l'instruction
   ``pygame.event.wait()`` qui attend qu'un évènement soit produit par
   l'utilisateur avant d'y réagir. Dans notre cas, nous souhaitons
   que le simulateur continue à calculer les mouvements de la corde même
   si l'utilisateur reste inactif et ne déclenche aucun évènement.

   Nous allons donc utiliser un autre mécanisme, qui permet
   d'effectuer un nombre donné d'itérations de la boucle principale
   par seconde, même en l'absence d'évènements produits par
   l'utilisateur. Ce mécanisme est illustré dans le fragment de code
   suivant::

     images_par_seconde = 25
     horloge = pygame.time.Clock()

     while True:
         for evenement in pygame.event.get():
             if evenement.type == pygame.QUIT:
                 pygame.quit()
                 sys.exit();

	 # Autres opérations de la boucle principale ...
         # ...

	 pygame.display.flip()
	 horloge.tick(images_par_seconde)

   Dans ce code, l'expression ``pygame.event.get()`` retourne la liste
   des évènements survenus depuis le dernier appel à cette
   fonction. Dans le cas d'une absence d'évènement, l'évaluation de
   cette expression n'est pas bloquante comme l'était celle de
   ``pygame.event.wait()``, mais retourne une liste vide. Dans la
   suite du code, une boucle ``for`` traite séparément tous les
   évènements recueillis à chaque itération de la boucle principale.

   La dernière instruction ``horloge.tick(images_par_seconde)`` de la
   boucle principale met le programme en attente pendant un délai calculé
   de façon à ce que le nombre d'itérations par seconde ne dépasse pas
   la valeur fournie en argument (25 dans le cas présent).

3) Ajouter au programme un mécanisme permettant de retenir et
   d'afficher la configuration de la corde. Le plus simple consiste à
   gérer des tableaux ``hauteur_corde`` et ``vitesse_corde`` retenant
   pour chaque indice :math:`i` dans l'intervalle :math:`[0, N]` la
   coordonnée verticale :math:`y_i` et la vitesse verticale
   :math:`\dot{y}_i` du point correspondant.  Initialement, on peut
   fixer :math:`y_i = \dot{y}_i = 0` pour tout :math:`i`. Il est
   conseillé d'attribuer la valeur 100 à :math:`N`.
   
   Pour afficher la corde, le plus simple consiste à appeler une fonction
   dédiée ``afficher_corde()`` à chaque itération de la boucle principale.
   Cette fonction peut simplement visualiser chaque point sous la forme d'un
   petit disque, et relier les points voisins par des segments de droite.
   L'idée est d'arriver à un affichage similaire à celui-ci:

   .. figure:: figures/prog-15-1-screenshot.png
     :scale: 50%
     :align: center
     :alt: Affichage de la corde. 

     Affichage de la corde.

   *Notes:*

   - Dans cet exemple, la corde a été mise en mouvement afin que les
     segments reliant les points soient bien visibles. A ce stade, votre
     programme doit afficher une corde entièrement horizontale.

   - Pour pouvoir effectuer les calculs avec des unités conventionnelles
     (longueurs en mètres, masses en kilogrammes, temps en secondes et
     forces en Newton), les hauteurs :math:`y_i` seront exprimées en mètres.
     Pour convertir ces hauteurs en pixels en vue de les afficher, on
     se basera sur les consignes suivantes: 

     * Les pixels sont carrés. En d'autres termes, un déplacement horizontal
       ou vertical du même nombre de pixels représente la même longueur.

     * La largeur de la fenêtre corrrespond à une distance totale de 1 mètre.

     * Une hauteur de corde égale à zéro correspond à la moitié de la hauteur
       de la fenêtre (comme l'extrémité droite de la corde dans la figure
       précédente).

     * Les hauteurs positives représentent un déplacement vers le haut.
       (*Rappel:* Dans le système de coordonnées de la fenêtre, l'axe
       vertical est orienté de haut en bas.)

4) Modifier le programme de façon à ce que les mouvements de la souris
   déterminent la position :math:`y_0` de l'extrémité gauche de la corde.

   *Principes:*

   - Les évènements de type ``pygame.MOUSEMOTION`` renseignent chaque
     mouvement de la souris. Ces évènements possèdent un champ ``pos``
     qui fournit les coordonnées ``(x, y)`` de la souris dans le repère
     de la fenêtre.

   - Dans la boucle principale de votre programme, lorsqu'un tel
     évènement est détecté, il faut modifier la valeur courante
     de :math:`y_0` de telle façon que:

     * Une position au milieu de la hauteur de la fenêtre corresponde à
       :math:`y_0 = 0`.

     * Une position dans la première ligne de la fenêtre (en haut) corresponde
       à :math:`y_0 = 1/4`.

     * Une position dans la dernière ligne de la fenêtre (en bas) corresponde
       approximativement à :math:`y_0 = -1/4`.  
    
5) Nous allons maintenant simuler le mouvement des points de la corde.
   Afin d'arriver à une simulation suffisamment précise, l'idée consiste
   à effectuer plusieurs pas de simulation et non un seul à chaque
   rafraîchissement de l'image:

   a) Définir une constante ``PAS_SIMULATION`` représentant le pas de
      temps, exprimé en millisecondes, séparant deux calculs. On peut
      par exemple définir ``PAS_SIMULATION = 10``, qui signifie que
      4 étapes de simulation seront effectuées pour chaque rafraîchissement
      (étant donné que ce dernier suit un rythme de 25 images par seconde).

   b) Dans la boucle principale du programme, avant d'appeler la fonction
      chargée d'afficher la corde, évaluer ``pygame.time.get_ticks()`` qui
      retourne l'instant courant, exprimé en millisecondes. Placer cette
      valeur dans une variable appelée ``temps_maintenant``. Ajouter
      également à votre boucle principale un mécanisme permettant de
      connaître la précédente valeur ``temps_precedent`` de
      ``temps_maintenant`` (c'est-à-dire, la valeur qu'avait  ``temps_maintenant``
      lors de l'itération précédente).

   c) Toujours dans la boucle principale, effectuer une boucle qui
      énumère toutes les valeurs du temps depuis ``temps_precedent +
      PAS_SIMULATION`` jusqu'à ``temps_maintenant``, avec un pas égal
      à ``PAS_SIMULATION``. Pour chaque valeur ``t`` du temps, appeler une
      fonction ``mettre_a_jour()`` chargée de mettre à jour la corde.
      Cette fonction doit recevoir comme argument la valeur de ``t``
      **convertie en secondes**.

   d) Implémenter la fonction ``mettre_a_jour()``. Celle-ci doit appliquer
      l'équation :math:`(4)` à l'ensemble des points de la corde d'indice
      compris entre :math:`0` et :math:`N - 1`.

      *Notes:*

      - L'équation :math:`(4)` fournit l'accélération verticale instantanée
	:math:`\ddot{y}_i` du point :math:`p_i` à l'instant :math:`t`. Cette
	accélération permet de mettre à jour la vitesse verticale
	:math:`\dot{y}_i` de ce point en effectuant

	  :math:`\dot{y}_i \leftarrow \dot{y}_i + \ddot{y}_i \delta_t`,

        où :math:`\delta_t` est l'incrément de temps (en secondes) depuis
	l'invocation précédente de ``mettre_a_jour()``. Cette vitesse verticale
	permet ensuite de mettre à jour la position verticale :math:`{y}_i`
	du point en effectuant

	  :math:`{y}_i \leftarrow {y}_i + \dot{y}_i \delta_t`.

      - Attention, il est important d'appliquer l'équation :math:`(4)` en
	considérant la position :math:`y_i` des points de la corde **avant
	la mise à jour**. (Il est facile de se tromper: Si l'on balaie les
	points de :math:`y_1` à :math:`y_{N-1}`, on pourrait être tenté
	d'utiliser pour mettre à jour :math:`y_i` la valeur de :math:`y_{i-1}`
	déjà mise à jour à l'étape précédente.)

	Pour résoudre ce problème, une solution simple consiste à d'abord
	recopier l'entièreté du tableau contenant la position verticale
	des points dans un autre tableau, et de consulter ce dernier lorsque
	l'on applique l'équation :math:`(4)`.

      - La densité de la corde peut etre fixée à 0.2 kg/m, et sa tension à
	0.01 Newton. Pour rappel, la longueur de la corde (correspondant
	à :math:`N \Delta`) a été fixée à 1 m.
	
6) Tester votre programme. En bougeant la souris, êtes-vous capable
   de créer des perturbations qui se propagent le long de la corde?
   Ces perturbations se réfléchissent-elles bien lorsqu'elles
   atteignent les points d'attache de la corde?

7) Vous pouvez à présent effectuer l'expérience suivante. En observant
   la propagation d'une perturbation, il est possible d'estimer le
   temps nécessaire pour qu'elle parcoure la longueur de la corde, et
   d'en déduire la vitesse de l'onde. (Pour effectuer cette mesure du
   temps de propagation, vous pouvez utiliser un chronomètre externe, ou bien
   en programmer un dans votre programme.)

   Qu'observez-vous? La valeur mesurée correspond-t-elle à la vitesse 
   :math:`\displaystyle\sqrt{\frac{T}{\mu}}` obtenue à la section
   précédente? N'hésitez pas à expérimenter avec d'autres valeurs de
   la tension et de la densité de la corde, afin de déterminer si la
   simulation permet de reproduire les prédictions de la théorie.

Amortissement et mécanismes annexes
-----------------------------------

A ce stade, la simulation des mouvements de la corde effectuée par
l'outil manque de réalisme, car elle néglige les effets dissipatifs
présents dans tout dispositif physique. Par exemple, une corde de
guitare ou de violon mise en vibration transfère une partie de son
énergie au reste de l'instrument et à l'onde sonore produite par
celui-ci, ce qui conduit à ce que cette vibration s'atténue avec
le temps.

Pour modéliser cela, une solution simple consiste à ajouter un
terme d'*amortissement* à notre équation du mouvement. On peut
ainsi considérer que chaque point :math:`p_i = (x_i, y_i)` de la
corde, pour :math:`i = 1, 2, \ldots, N - 1`, est soumis à une
force additionnelle d'amortissement

  :math:`f_a = -c \dot{y}_i`

orientée verticalement, proportionnelle à la vitesse verticale
:math:`\dot{y}_i` du point, et s'opposant à celle-ci.

Marche à suivre:

1) Mettre à jour l'équation :math:`(4)` de façon à tenir compte
   de cette nouvelle force d'amortissement.

2) Modifier la fonction ``mettre_a_jour()`` de votre programme
   en conséquence. Pour la constante d'amortissement :math:`c`
   intervenant dans l'expression de la force d'amortissement,
   vous pouvez utiliser la valeur de 0.0001 kg/s.

3) Vérifier le bon fonctionnement du programme.

Remise à zéro
~~~~~~~~~~~~~

Cette étape consiste à ajouter au programme un mécanisme permettant
de retendre la corde, c'est-à-dire de remettre à zéro la position
verticale de tous ses points. Pour ce faire, le plus simple est
de détecter les frappes au clavier, en gérant les évènements de
type ``pygame.KEYDOWN``. Chaque évènement de ce type possède un
champ ``key`` renseignant la touche qui vient d'être pressée,
par exemple ``pygame.K_r`` pour la touche 'R' (comme *Reset*).
Vous pouvez donc:

1) Modifier le code responsable de la gestion des évènements de
   façon à appeler une nouvelle fonction ``gerer_touche()``
   chaque fois qu'une touche du clavier est pressée.

2) Passer à cette fonction un argument correspondant à la touche
   pressée.

3) Dans la fonction ``gerer_touche()``, si la touche est un 'R', alors
   remettre à zéro la hauteur et la vitesse de tous les points de la
   corde.

4) Tester soigneusement votre programme.

Modes de fonctionnement supplémentaires
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

En détectant d'autres touches, il est facile d'implémenter plusieurs
modes de fonctionnement du programme:

- Un mode "souris" (touche 'S'), que vous avez déjà implémenté, dans
  lequel les mouvements de la souris sont transmis à l'extrémité gauche
  de la corde.

- Un mode "impulsion" (touche 'I'), dans lequel chaque clic de la souris
  produit une impulsion de durée déterminée à l'extrémité gauche de la
  corde.

- Un mode "harmonique" (touche 'H'), dans lequel un mouvement harmonique
  est automatiquement imprimé à l'extrémité gauche de la corde. La fréquence
  de ce mouvement peut être modifiée en pressant les touches "flèche
  gauche" (ralentir) et "flèche droite" (accélérer).

La procédure à suivre est la suivante:

1) Définir une nouvelle variable ``mode`` dont la valeur détermine le
   mode courant de fonctionnement. Une bonne idée est de définir également
   trois constantes correspondant aux trois modes précités (peu importe
   leur valeur). Initialement,
   le simulateur est en mode "souris".

2) Implémenter la détection des touches 'S', 'I' et 'H' (en addition à
   la touche 'R' déjà traitée), de façon à changer le mode courant de
   fonctionnement lorsqu'elles sont pressées.

3) Afficher à l'écran le mode courant de fonctionnement, par exemple
   en vous basant sur le fragment de code suivant::

     # A n'exécuter qu'une fois avant de rentrer dans la boucle principale:

     police  = pygame.font.SysFont("monospace", 16)
     couleur = (0, 0, 0)  # Noir
     
     # ...

     # A effectuer à chaque affichage:

     image = police.render("mon texte", True, couleur)
     fenetre.blit(image, (x, y))

   *Note:* Pour éviter que votre code ne devienne redondant, une bonne
   idée est de placer les dernières instructions dans une fonction dédiée à
   l'affichage de texte.

4) Ne pas oublier de modifier la gestion de l'évènement ``pygame.MOUSEMOTION``
   de façon à ce que les mouvements de la souris ne soient pris en compte
   que dans le mode "souris".

5) Tester que le mécanisme de changement de mode fonctionne.

6) Implémenter le mode "impulsion". Lorsque ce mode est actif, le programme
   doit détecter les évènements de type ``pygame.MOUSEBUTTONDOWN`` pour
   lesquels le champ ``button`` vaut 1 (correspondant au bouton de gauche
   de la souris). Lorsqu'un tel clic est repéré, le simulateur doit lever
   l'extrémité gauche de la corde (c'est-à-dire attribuer une valeur
   donnée à :math:`y_0`) pendant un temps donné.

   Cette opération dépend donc de deux paramètres:

   - L'amplitude de l'impulsion, correspondant à la hauteur à laquelle
     la corde est levée. Vous pouvez attribuer la valeur de 0.2 m à
     ce paramètre.

   - La largeur de l'impulsion, correspondant au délai pendant lequel
     la corde est levée à chaque impulsion. Une suggestion est d'employer
     une valeur de 0.050 s.

7) Vérifier que le mode "impulsion" fonctionne comme attendu.

8) Implémenter le mode "harmonique". Lorsque celui-ci est actif,
   l'extrémité gauche de la corde doit suivre un mouvement sinusoïdal
   d'amplitude et de fréquence données. Une manière simple d'implémenter
   ce mécanisme est la suivante:

   a) Définir une variable globale ``frequence`` donnant la fréquence
      courante de l'oscillateur harmonique. Dans un premier temps,
      une fréquence de 0.5 Hz peut être utilisée. (Nous implémenterons
      plus tard le mécanisme permettant de faire varier cette fréquence.)

   b) Définir une variable gobale ``alpha`` retenant la phase courante
      de l'oscillateur harmonique (c'est-à-dire l'angle en radians qui
      sera utilisé pour calculer le mouvement sinusoïdal de l'oscillateur).
     
   c) Dans ``mise_a_jour()``, si le mode harmonique est actif, mettre
      à jour ``alpha`` en fonction de l'incrément de temps depuis
      l'invocation précédente et de la valeur courante de ``frequence``.
      Mettre ensuite à jour :math:`y_0` en multipliant le sinus de ``alpha``
      par l'amplitude de l'oscillateur. Cette amplitude peut être
      fixée à 0.1 m.

   d) Dans le mode harmonique, afficher la valeur courante de la
      fréquence à l'écran. Vous pouvez utiliser la construction
      suivante, produisant une chaîne de caractères dans laquelle
      ``frequence`` est formaté avec quatre chiffres
      après la virgule::

	"Frequence : {0:.4f} Hz".format(frequence)

   e) Détecter les frappes des touches "flèche gauche" (``pygame.KEY_LEFT``)
      et "flèche droite" (``pygame.KEY_RIGHT``), et modifier la fréquence
      courante de façon appropriée (par exemple en la divisant
      ou multipliant par 1.001).

      Pour que ce mécanisme soit pratique à utiliser, une bonne idée est
      d'exécuter::
      
        pygame.key.set_repeat(10, 10)
     
      avant de rentrer dans la boucle principale du programme. Cette
      instruction modifie le délai après lequel un appui prolongé sur une
      touche du clavier genère des évènements répétés.

9) Vérifier que le mode "harmonique" est fonctionnel. Vous devrier obtenir
   un affichage proche de celui-ci:

   
   .. figure:: figures/prog-15-2-screenshot.png
     :scale: 50%
     :align: center
     :alt: Mode harmonique. 

     Mode harmonique.

10) Déposer votre programme dans le répertoire centralisé, sous le
    suffixe ``prog-15.py``.
     
Expériences
-----------

Cette dernière étape consiste à utiliser le simulateur que vous avez
développé pour effectuer quelques expériences.

La propagation d'une impulsion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Le mode "impulsion" permet de mettre en évidence la réponse en
fréquence limitée du système: L'impulsion générée par votre programme
correspond à un signal constant pendant un intervalle, et nul le
reste du temps, mais celui qui est propagé le long de la corde n'a
pas exactement cette forme. Ce signal contient en effet des composantes
à haute fréquence, associés à des changements brusques de hauteur,
que le système atténue fortement.

Un autre phénomène facile à observer est le retournement du signal lorsqu'il
se réfléchit aux
extrémités fixes de la corde. Si vous modifiez le code du simulateur
de façon à ce que l'extrémité droite de la corde reste libre de se
déplacer verticalement, vous observerez un comportement
différent. (*Note:* Cette modification n'est pas triviale, car elle
nécessite d'employer une version adaptée de l'équation :math:`(4)`
pour le point situé à l'extrémité droite de la corde. Vous n'êtes pas
obligé de l'implémenter.)

La résonance
~~~~~~~~~~~~

Lorsque le simulateur est en mode "harmonique", il est intéressant d'observer
l'influence du choix de la fréquence de l'oscillateur sur l'amplitude des
mouvements de la corde. Pour certaines fréquences, cette amplitude est du
même ordre que celle de l'oscillateur harmonique (que nous avions fixée à
0.1 m). Pour d'autres, l'amplitude devient beaucoup plus importante.
*Note:* Pour bien observer cela, il faut attendre, après une modification
de la fréquence, un temps suffisant pour que les signaux transitoires
aient disparu.

Le phénomène qui permet d'obtenir un signal d'amplitude plus élevée
que celle de l'oscillateur utilisé pour exciter la corde est appelé
*résonance*. Il possède de nombreuses applications; c'est notamment
grâce à lui que la plupart des instruments de musique produisent leur
son. Une façon de le mettre en évidence à l'aide du simulateur
consiste à régler l'oscillateur sur la fréquence naturelle de la
corde, que l'on peut déterminer soit empiriquement (seriez-vous
capable de trouver expérimentalement la fréquence conduisant à
l'amplitude la plus grande?), soit par un calcul:

1) Pour rappel, la vitesse de propagation des ondes sur la corde vaut

    :math:`\displaystyle v = \sqrt{\frac{T}{\mu}}`,

   où :math:`T` et :math:`\mu` désignent respectivement la tension
   et la densité de la corde.

2) Notons :math:`L` la longueur de la corde (égale à 1 m dans notre
   simulation). Le délai nécessaire
   à un aller et retour d'une perturbation sur la corde est égal à:

    :math:`\displaystyle \frac{2L}{\sqrt{\frac{T}{\mu}}}`

3) Pour que l'oscillateur harmonique possède une période égale à ce
   délai, sa fréquence doit être égale à:

    :math:`\displaystyle \frac{\sqrt{\frac{T}{\mu}}}{2L}`.

Vous pouvez donc calculer cette valeur, et essayer de l'introduire dans
votre programme. Le phénomène de résonance devient-il visible? Est-ce
le cas également lorsque vous utilisez un multiple entier de la
fréquence calculée? Comment cela s'explique-t-il? Pouvez-vous aussi
calculer une fréquence conduisant à l'amplitude minimale du signal?

Conclusions
-----------

Si vous avez atteint ce point, félicitations, vous êtes arrivé au terme
de ces laboratoires. Nous espérons que ceux-ci vous ont permis de découvrir
quelques applications intéressantes des notions que vous avez étudiées dans
d'autres cours, et de renforcer votre expérience en programmation. Merci
enfin d'avoir participé aussi activement aux séances.
Si vous avez des suggestions sur la façon d'améliorer ces laboratoires,
n'hésitez pas à les transmettre.