Un robot MindStorm EV3 musicien (en Python)


Généralités sur la musique électronique

Un son est une vibration. La note dépend de la fréquence émise. Pour synthétiser une note en électronique, on utilise un séquenceur. C'est un composant électronique qui génère un signal ondulatoire selon une fréquence donnée. En informatique, il existe des séquenceurs suffisamment  précis pour être utilisés dans la fabrication d’instruments de musique. Chaque ordinateur moderne est équipé d'un séquenceur MIDI capable de moduler le signal pour imiter tous les instruments de musique et même de superposer plusieurs signaux pour simuler tout un orchestre.
Ces composants sont toutefois limités musicalement. Le séquencement lui-même n'est pas instantané. Le fraction infime de temps, qui dépend de la qualité des composants électroniques, induit une légère imprécision sur les fréquences générées, surtout dans les hautes fréquences qui approchent la fréquence de l'horloge qui cadence le système électronique. Par ailleurs, certaines fréquences  peuvent se trouver en résonance avec l'horloge du système, ou générer des effets de moirés avec celle-ci, induisant des fréquences parasites qu'il est difficile de filtrer. Aussi, les fréquences utilisées pour la musique électronique sont celles de la gamme chromatique accordée sur le La de 440 Hertz. De plus, le calcul en virgule flottante est extrêmement coûteux en temps machine, ce qui induit une dérive sur le tempo surtout pour les notes très rapides. Aussi les fréquences sont-elles exprimées en nombre entier. Cependant, la (mauvaise) qualité musicale de la brique EV3 n'exige pas de précision supérieure et les fréquences obtenues constituent une excellente approximation.
Pour ceux qui seraient déçus par ce manque de précision, le principe de calcul de ces fréquences fait l'objet d'un article dédié intitulé «Musique & Mathématiques ».

Méthodes de la classe Sound pour jouer de la musique

La classe Sound dispose de de deux méthodes pour jouer un morceau de musique. Elles permettent d’enchaîner les notes définies dans un tableau passé en paramètre dans une boucle pour réduire la dérive de tempo induite par l'interpréteur Python du fait de l'interprétation des boucles et du calcul des clauses de répétition dans ce langage.

La méthode tone()

La méthode tone() fait le pendant de la méthode play_tone(). Elle est déclarée en Python comme ci-dessous :
tone(*args, play_type=0)
Le morceau de musique est passé par le paramètre args.  Celui-ci est exprimé sous la forme d'un tableau de tuples, chaque tuple contenant trois valeurs :
  • La fréquence en Hertz de la note.
  • La durée en millisecondes.
  • La durée muette (sans son) qui suit la note, pour générer une attaque sur la note suivante.
Voici, ci-dessous, un exemple de l'utilisation de cette méthode.
#!/usr/bin/env python3

from ev3dev2.sound import Sound

HP = Sound()

DARK_VADOR = [
    (392, 350, 100), (392, 350, 100), (392, 350, 100), (311.1, 250, 100),
    (466.2, 25, 100), (392, 350, 100), (311.1, 250, 100), (466.2, 25, 100),
    (392, 700, 100), (587.32, 350, 100), (587.32, 350, 100),
    (587.32, 350, 100), (622.26, 250, 100), (466.2, 25, 100),
    (369.99, 350, 100), (311.1, 250, 100), (466.2, 25, 100), (392, 700, 100),
    (784, 350, 100), (392, 250, 100), (392, 25, 100), (784, 350, 100),
    (739.98, 250, 100), (698.46, 25, 100), (659.26, 25, 100),
    (622.26, 25, 100), (659.26, 50, 400), (415.3, 25, 200), (554.36, 350, 100),
    (523.25, 250, 100), (493.88, 25, 100), (466.16, 25, 100), (440, 25, 100),
    (466.16, 50, 400), (311.13, 25, 200), (369.99, 350, 100),
    (311.13, 250, 100), (392, 25, 100), (466.16, 350, 100), (392, 250, 100),
    (466.16, 25, 100), (587.32, 700, 100), (784, 350, 100), (392, 250, 100),
    (392, 25, 100), (784, 350, 100), (739.98, 250, 100), (698.46, 25, 100),
    (659.26, 25, 100), (622.26, 25, 100), (659.26, 50, 400), (415.3, 25, 200),
    (554.36, 350, 100), (523.25, 250, 100), (493.88, 25, 100),
    (466.16, 25, 100), (440, 25, 100), (466.16, 50, 400), (311.13, 25, 200),
    (392, 350, 100), (311.13, 250, 100), (466.16, 25, 100),
    (392.00, 300, 150), (311.13, 250, 100), (466.16, 25, 100), (392, 700, 0)
    ]


HP.tone(DARK_VADOR)


La méthode play_song()

La méthode play_song() fait le pendant à la méthode play_note(). Elle est déclarée en Python comme ci-dessous :
play_song(song, tempo=120, delay=0.05)
Les paramètres de cette méthode sont les suivants :
  • song correspond au morceau de musique. Celui-ci est exprimé comme un tableau de tuples. Chaque tuple contient deux valeurs :
    • La première défini la note. Celle-ci est une chaîne de caractères interprétée dans le dictionnaire  _NOTE_FREQUENCIES (voir paragraphe traitant des constantes de la classe Sound ci-dessous).
    • La seconde défini la durée de la note. Celle-ci est une chaîne de caractères interprétée dans le dictionnaire _NOTE_VALUES.
  • tempo défini le tempo du morceau de musique. La valeur par défaut 120 correspond au tempo Allegro. Musicalement parlant, cela correspond approximativement 120 noires par minute, soit deux noires par seconde.
  • delay définit le temps en millisecondes entre chaque note. Ce paramètre permet de simuler une attaque pour chaque note. Mais il induit une petite dérive sur le tempo.
Voici, ci dessous, un exemple de l'utilisation de cette méthode :
#!/usr/bin/env python3

from ev3dev2.sound import Sound

HP = Sound()

STARS_WAR =(
    ('D4', 'e3'),      # intro
    ('D4', 'e3'),
    ('D4', 'e3'),
    ('G4', 'h'),       # 1ere mesure
    ('D5', 'h'),
    ('C5', 'e3'),      # 2eme mesure
    ('B4', 'e3'),
    ('A4', 'e3'),
    ('G5', 'h'),
    ('D5', 'q'),
    ('C5', 'e3'),      # 3eme mesure
    ('B4', 'e3'),
    ('A4', 'e3'),
    ('G5', 'h'),
    ('D5', 'q'),
    ('C5', 'e3'),      # 4eme mesure
    ('B4', 'e3'),
    ('C5', 'e3'),
    ('A4', 'h.'),
    )

HP.play_song(STARS_WAR)

Les constantes de la classe Sound

L'interprétation des chaines de caractères fournies aux méthodes play_note() et play_song() de la classe Sound s'appuie sur deux dictionnaires _NOTE_FREQUENCIES et _NOTE_VALUES définis en variables de classe. Le souligné en préfixe indique que ces deux dictionnaires sont déclarés protected, et donc ne sont pas accessibles à partir de l'instance HP. Ils sont néanmoins accessible en dérivant la classe Sound comme le décrit l'article intitulé « Accéder aux dictionnaires de musique de la classe Sound».

La constante _NOTE_FREQUENCIES

Le séquenceur de la brique EV3 est assez rudimentaire. De plus l'horloge interne de la brique est relativement faible. De ce fait, le calcul des fréquences des notes ne peut être effectuée en temps réel. En effet ce calcul nécessite des opérations répétitives qui prennent du temps. Ce qui donne un effet saccadé lorsqu'un morceau est joué qui entraîne une dérive importante sur le tempo. Aussi, toutes les fréquences de toutes les notes de toutes les octaves sont pré-calculées et rangées dans un dictionnaire Python déclaré dans la classe Sound par la constante de classe _NOTE_FREQUENCIES. Le contenu de ce dictionnaire est présenté dans le tableau ci-dessous :

NoteOctave 0Octave 1Octave 2Octave 3Octave 4Octave 5Octave 6Octave 7Octave 8
Do'C0': 16'C1': 33'C2': 65'C3': 131'C4': 262'C5': 523'C6': 1046'C7': 2093'C8': 4186
Do#
Réb 
'C#0': 17
'Db0': 17
'C#1': 35
'Db1': 35
'C#2': 69
'Db2': 69
'C#3': 139
'Db3': 139
'C#4': 277
'Db4': 277
'C#5': 554
'Db5': 554
'C#6': 1109
'Db6': 1109
'C#7': 2217
'Db7': 2217
'C#8': 4435'
Db8': 4435
'D0': 18'D1': 37'D2': 73'D3': 147'D4': 294'D5': 587'D6': 1175'D7': 2349'D8': 4699
Ré#
Mib
'D#0': 19
'Eb0': 19
'D#1': 39
'Eb1': 39
'D#2': 78
'Eb2': 78
'D#3': 156
'Eb3': 156
'D#4': 311
'Eb4': 311
'D#5': 622
'Eb5': 622
'D#6': 1245
'Eb6': 1245
'D#7': 2489
'Eb7': 2489
'D#8': 4978
'Eb8': 4978
Mi'E0': 21'E1': 41'E2': 82'E3': 165'E4': 330'E5': 659'E6': 1319'E7': 2637'E8': 5274
Fa'F0': 22'F1': 44'F2': 87'F3': 175'F4': 349'F5': 698'F6': 1397'F7': 2794'F8': 5588
Fa#
Solb
'F#0': 23
'Gb0': 23
'F#1': 46
'Gb1': 46
'F#2': 92
'Gb2': 92
'F#3': 185
'Gb3': 185
'F#4': 370
'Gb4': 370
'F#5': 740
'Gb5': 740
'F#6': 1480
'Gb6': 1480
'F#7': 2960
'Gb7': 2960
'F#8': 5920
'Gb8': 5920
Sol'G0': 24'G1': 49'G2': 98'G3': 196'G4': 392'G5': 784'G6': 1568'G7': 3136'G8': 6272
Sol#
Lab
'G#0': 26
'Ab0': 26
'G#1': 52
'Ab1': 52
'G#2': 104
'Ab2': 104
'G#3': 208
'Ab3': 208
'G#4': 415
'Ab4': 415
'G#5': 831
'Ab5': 831
'G#6': 1661
'Ab6': 1661
'G#7': 3322
'Ab7': 3322
'G#8': 6645
'Ab8': 6645
La'A0': 28'A1': 55'A2': 110'A3': 220'A4': 440'A5': 880'A6': 1760'A7': 3520'A8': 7040
La#
Sib
'A#0': 29
'Bb0': 29
'A#1': 58
'Bb1': 58
'A#2': 117
'Bb2': 117
'A#3': 233
'Bb3': 233
'A#4': 466
'Bb4': 466
'A#5': 932
'Bb5': 932
'A#6': 1865
'Bb6': 1865
'A#7': 3729
'Bb7': 3729
'A#8': 7459
'Bb8': 7459
Si'B0': 31'B1': 62'B2': 123'B3': 247'B4': 494'B5': 988'B6': 1976'B7': 3951'B8': 7902

La constante _NOTE_VALUES

Pour exprimer la durée des notes pour la méthode play_song(), une codification est aussi définie dans un dictionnaire Python déclaré dans la classe Sound par la constante de classe _NOTES_VALUES. Le tableau ci-dessous présente cette codification :
NoteCodeDurée
Ronde'w'1,0000
Blanche'h'0,5000
Noire'q'0,2500
Croche'e'0,1250
Double-croche's'0,0625

Il est également possible de suffixer les codes pour obtenir des durées musicales comme les notes pointées ou les triolets :
  • Le caractère de division / peut être utilisé pour les triolets. Par exemple, 'q/3' indique une croche dans un triolet de croche (la durée de la noire divisée par 3).
  • Le caractère  de multiplication * peut être utilisé pour les notes pointées. Par exemple, 'q*1.5' indique une noire pointée (la durée d'une noire + la durée d'une croche).
  • Le caractère 3 est utilisé aussi pour un triolet. Par exemple, 'e3' est la durée d'une croche dans un triolet.
  • Le caractère . est aussi utilisé pour les notes pointées. Par exemple, 'q.' est la durée d'un noire pointée.
Le dictionnaire _NOTE_VALUE ne propose pas de durée pour les triples et quadruples croches. Si c'est nécessaire, on peut utiliser l'opérateur / sur la durée de la double croche, 's/2' pour la triple croche, 's/4' pour la quadruple croche. Cependant, les durées ainsi obtenue s'approche de la fréquence de l'horloge interne de la brique EV3. Ce qui va générer des dérives importantes sur le tempo de la musique.

Commentaires

Posts les plus consultés de ce blog

Connecter ev3dev2 à Internet en WiFi

Connecter Visual Studio Code à un robot MindStorm EV3 avec ev3dev-browser

Installer les modules EV3DEV2 sur Python