Scripts MIDI-PLAY-WRITE avec Python, MidO et RtMidi (3/6)
Initiation à la programmation MIDI (Musical Instrument Digital Interface) en utilisant le trio « Python – MidO – RtMidi », avec focus sur MIDI-PLAY et MIDI-WRITE
♦ Précision et Prérequis
MidO (Midi Objects) nous permet de travailler avec des messages MIDI directement en tant qu’objets Python. Après avoir vu « MIDI-OUT » dans un précédent article, ici nous continuons avec « MIDI-PLAY » et « MIDI-WRITE », et dans un prochain article nous finirons par « MIDI-IN ».
Dans cet article nous allons découvrir (que) quelques éléments de base pour jouer et écrire des fichiers MIDI avec MidO, et RtMidi, son backend par défaut vers les entrés MIDI de Qsynth/FluidSynth.
Pour de plus amples d’informations, reportez-vous aux documentations ad-hoc dans les deux liens ci-dessous.
• À lire en premier
- Introduction à la programmation MIDI avec Python, MidO et RtMidi (1/6)
Nous allons découvrir le trio « Python – MidO – RtMidi » et son environnement de développement, puis nous les utiliserons pour nous initier à la programmation MIDI (Musical Instrument Digital Interface) - Scripts MIDI-OUT avec Python, MidO et RtMidi (2/6)
Reportez-vous au début de cet article pour voir les prérequis, valables pour les quatre articles sur Python3 – MidO – RtMidi.
♦ MIDI-PLAY de MidO
Les objets MidiFile peuvent être utilisés pour lire (read), écrire (write) et jouer (play back) le contenu des fichiers MIDI.
Comme vous le constaterez en lisant la documentation de MidO, il y a plusieurs façons de faire les choses en fonction de ses besoins et de ses équipements. Ci-dessous nous prenons une voie parmi d’autres.
• JOUER un fichier .mid
Nous reprenons des éléments utilisés dans mon précédent article 2/6 et nous y ajoutons ceux spécifiques à la lecture d’un Fichier MIDI Standard (SMF – Standard MIDI Files). La spécification Fichiers MIDI standard définit comment enregistrer une séquence de messages MIDI dans un fichier (.mid) afin qu’ils puissent être lus dans le bon ordre et au bon moment.
Le script -MIDO Play Midi File- dans un éditeur de texte
# *** LECTURE d'un FICHIER MIDI *** print("===== JOUE un Fichier MIDI... =====") import mido # importe la bibliothèque MidO qui gère aussi RtMidi import time # importe le module Time Python # ouvre un port OUT-MidO et le connecte à IN QS-M2 Qsynth/FluidSynth port = mido.open_output('Synth input port (QS-M2_TofH-XG:0)') # chemin absolu vers le fichier .mid, ici "blackvelvet.mid" mid = mido.MidiFile('/home/joe/Python-Projects/Mido-Scripts/blackvelvet.mid') # affiche chemin fichier Midi + son type + nb de pistes + nb de messages dans fichier print("=>", mid, "...\n... ...") # calcul + affiche la durée de lecture du fichier Midi en h:m:s print("=> Durée de lecture =", time.strftime('%Hh:%Mm:%Ss', time.gmtime(mid.length))) print("=> Lecture en cours...") for msg in mid.play(): # boucle de lecture du fichier Midi port.send(msg) # envoi fichier Midi port MidO-OUT vers IN QS-M2 Qsynth/FS port.close() # ferme proprement le port Midi print("=> Fichier MIDI lu... ARRÊT !")
Astuce : Nous pouvons obtenir le temps total de lecture en secondes d’un fichier MIDI en accédant à la propriété length de MidO : mid.length. Puis, il suffit de le convertir en H:M:S.
Ceci n’est pris en charge que pour les fichiers MIDI de type 0 et 1. L’accès à length sur un fichier de type 2 génère ValueError car il est impossible de calculer le temps de lecture d’un fichier asynchrone.
Voir ci-dessous les explications sur les Types 0, 1 et 2 des SMF (Standard MIDI File).
La sortie du script -MIDO Play Midi File- dans une console Python
Python 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] on linux Type "help", "copyright", "credits" or "license()" for more information. >>> ======== RESTART: /home/joe/Python-Projects/Mido-Scripts/MIDO_Play-Midi-File.py ======== ===== JOUE un Fichier MIDI... ===== => <midi file '/home/joe/Python-Projects/Mido-Scripts/blackvelvet.mid' type 1, 11 tracks, 9347 messages> ... ... ... => Durée de lecture = 00h:04m:21s => Lecture en cours... ... ... => Fichier MIDI lu... ARRÊT ! >>>
Remarque : Il existe trois types de fichiers MIDI standard (SMF) :
– Type 0 (piste unique) : tous les messages sont sauvegardés dans une seule piste. Ce type est souvent utilisé par les claviers-synthétiseurs et autres claviers numériques avec séquenceur.
– Type 1 (synchrone) : les messages sont sauvegardés dans plusieurs pistes et toutes les pistes commencent en même temps. Ce type est le plus fréquemment utilisé par les séquenceurs et STAN (DAW) logiciels.
– Type 2 (asynchrone) : les messages sont sauvegardés dans plusieurs pistes et chaque piste est indépendante des autres. Ce type est très rarement utilisé en pur MIDI.
• • • • • • • • • •
Remix de Black Velvet chantée par Alannah Myles – 1989 (sous distribution GNU/Linux)
Fichier MIDI ~ 30 Ko lu par le script Python MidO -et-
enregistré en temps réel avec Audacity (Audio Jack) dans fichier audio OGG ~ 4,3 Mo
Python MidO |
Fichier 30 Ko |
MidO backend |
Synthétiseur logiciel |
Fonte sonore |
Audio num. |
Enregistreur numérique |
Fichier 4,3 Mo |
Script | .mid | RtMidi | Qsynth / FS | .sf2 | Jack | Audacity | .ogg |
• • • • • • • • • •
Capture d’écran du script -MIDO Play Midi File- en action
Le script ne prévoit pas, pour l’instant, l’envoi des messages MIDI vers le clavier virtuel VMPK. Nous verrons cela plus tard. Mais… grâce à Patchage, il est possible d’intervenir visuellement et facilement sur les connexions Audio/MIDI du système.
Il suffit de tirer à la souris une connexion MIDI compatible entre RtMidi Ouput et VMPK Input, et dans VMPK de cocher les cases Activer l’entrée MIDI et Mode MIDI Omni (réception sur les 16 canaux). Et voila, les notes jouées du fichier MIDI s’afficheront dynamiquement sur VMPK.
De plus, si nous utilisons au moins sa version 0.7, chaque canal (instrument) aura sa propre couleur. C’est plutôt sympathique, voire éducatif, que d’écouter un fichier MIDI et de voir en temps réel le jeu dynamique (intensité couleur proportionnelle à la vélocité) de l’artiste à l’écran. N’est-ce pas !
Nous aurions aussi pu utiliser autant de claviers virtuels que de canaux dans le fichier MIDI, en désactivant le Mode MIDI Omni dans VMPK, puis en lançant et disposant à l’écran autant d’occurrences de VMPK que nécessaire. Magique !
La version de VMPK (Virtual Midi Piano Keyboard) actuellement disponible dans la Logithèque de Linux Mint n’est que la 0.4.0 et qui commence à dater très fort (juin 2011 !). Pour disposer de VMPK 0.7+, il faudra le télécharger directement depuis le site de son développeur, pour Linux qu’en 64-bit au format AppImage (logiciel Tout-en-un (AIO) avec toutes ses dépendances).
– Linux (64 bit): vmpk-0.7.1-linux-x86_64.AppImage (24 MB) as of December 2018
Une fois téléchargé sur notre ordinateur, validons la case Autoriser l’exécution du fichier comme un programme dans les Propriétés-Permission du fichier, puis un double-clic sur le fichier lance directement l’application VMPK. Nous pouvons aussi créer une entrée dans le Menu Principal de Linux Mint en ouvrant Cinnamon Menu Editor, puis :
– Applications / Son et vidéo => clic sur bouton + Nouvel élément qui ouvre Launcher Properties
. Name: Virtual Midi Piano Keyboard √ (ou autre)
. Command: /home/joe/AppImages/vmpk-0.7.1-linux-x86_64.AppImage √ (fonction d’où nous avons enregistré le fichier)
. Comment: VMPK – un générateur et récepteur d’événements MIDI (ou autre)
. Pour finir, clic sur le bouton √ Valider. Et voila !
♦ MIDI-WRITE de MidO
Nous pouvons créer un nouveau fichier toujours en appelant MidiFile sans l’argument du nom de fichier. Le fichier peut ensuite être enregistré en appelant la méthode save ().
• ÉCRIRE (dans) un fichier .mid
La classe MidiTrack est une sous-classe de la liste, nous pouvons donc utiliser toutes les méthodes habituelles. Tous les messages doivent être marqués avec le temps delta (en ticks). Un delta est le temps d’attente avant le message suivant. S’il n’y a pas de message ‘end_of_track’ à la fin d’une piste, un message sera quand même écrit.
Le script -MIDO Write Midi File- dans un éditeur de texte
# ÉCRITURE d'un fichier MIDI # GAMME CHROMATIQUE UP sur 6 octaves (Do1 à Do7) + 73 Program Changes GM import mido # importe bibliothèque Mido from mido import Message, MidiFile, MidiTrack # importe modules depuis Mido import time # importe le module Time Python import random # importe le module Random Python mid = MidiFile() # Nous pouvons créer un nouveau fichier en appelant MidiFile track = MidiTrack() # sans l’argument du nom de fichier. Le fichier peut ensuite mid.tracks.append(track) # être enregistré à la fin en appelant la méthode save() n = 24 # initialisation variable n avec note 24 = Do1 p = 0 # initialisation variable p avec program 0 = Grand Piano while n < 97: # boucle notes à jouer dans la gamme Chromatique 24 à 96 / Do1 à Do7 alea = random.randrange(1,8) # générateur valeurs aléatoires entre 1 et 7 # affiche les numéros de note / de program-instrument joués print("Note", n, "- Instrument", p, "- alea", alea) track.append(Message('program_change', program=p, time=0)) # n. program=instrument track.append(Message('note_on', note=n, velocity=100, time=32)) track.append(Message('note_off', note=n, velocity=67, time=128 *alea)) n = n +1 # incrémentation i (note) p = p +1 # incrémentation j (program-instrument) print("Note", 60, "- Instrument", 64, "- Final touch Soprano Sax") track.append(Message('program_change', program=64, time=0)) # Final touch Soprano Sax track.append(Message('note_on', note=60, velocity=127, time=128)) track.append(Message('note_off', note=60, velocity=67, time=1024)) mid.save('MIDO_Write-Midi-File.mid') # enregistre le tout dans ce fichier Midi print("=> Fichier MIDI sauvegardé !\n", mid, "...") # affiche info fichier Midi
Remarque : L’attribut time est utilisé de différentes manières :
– dans une piste, c’est le temps delta en ticks. Cela doit être un entier,
– dans les messages générés par play(), il s’agit du temps delta en secondes (temps écoulé depuis le dernier message renvoyé), et
– dans certaines méthodes (important uniquement pour les développeurs), il est utilisé pour le temps absolu en ticks ou en secondes.
La sortie du script -MIDO Write Midi File- dans une console Python
Python 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] on linux Type "help", "copyright", "credits" or "license()" for more information. >>> ======= RESTART: /home/joe/Python-Projects/Mido-Scripts/MIDO_Write-Midi-File.py ======= Note 24 - Instrument 0 - alea 5 Note 25 - Instrument 1 - alea 6 Note 26 - Instrument 2 - alea 4 Note 27 - Instrument 3 - alea 2 Note 28 - Instrument 4 - alea 2 Note 29 - Instrument 5 - alea 4 Note 30 - Instrument 6 - alea 4 ... ... ... ... ... ... ... ... Note 90 - Instrument 66 - alea 1 Note 91 - Instrument 67 - alea 4 Note 92 - Instrument 68 - alea 5 Note 93 - Instrument 69 - alea 5 Note 94 - Instrument 70 - alea 7 Note 95 - Instrument 71 - alea 5 Note 96 - Instrument 72 - alea 2 Note 60 - Instrument 64 - Final touch Soprano Sax => Fichier MIDI sauvegardé ! <midi file None type 1, 1 tracks, 222 messages> ... >>>
Précision : La synchronisation dans les fichiers MIDI est centrée sur les ticks et les battements (tempo en BPM). Un temps est la même chose qu’une noire (dans une mesure chiffrée 4/4, la noire vaut un temps, soit le quart de la mesure). Les battements sont divisés en ticks, la plus petite unité de temps en MIDI.
Contrairement à la musique physique, le tempo MIDI n’est pas défini en battements par minute, mais en microsecondes par battement. Le tempo par défaut est de 500.000 microsecondes par battement, soit 120 battements par minute (BPM). Le méta-message ‘set_tempo’ peut être utilisé pour changer de tempo pendant une chanson. Nous pouvons utiliser bpm2tempo() et tempo2bpm() pour convertir vers et depuis battements par minute. Notons que tempo2bpm() peut renvoyer un nombre à virgule flottante.
• • • • • • • • • •
La sortie du script sur les hauts-parleurs de mon ordinateur pour une musique toujours cacophonique (ce sont toujours des tests, quoi que)
Fichier MIDI ~ 1 Ko crée avec le script Python MidO -puis-
lu et converti directement par VLC en fichier audio OGG ~ 700 Ko
• • • • • • • • • •
• ÉCRIRE une petite composition dans un fichier .mid
Pour compléter mon précédent exemple, nous allons y ajouter une petite composition dont nous avons transformé la partition musicale en valeurs numériques que MidO peut gérer. Comme toujours, cet exemple n’est qu’une manière de faire parmi d’autres.
Le script -MIDO Write Nocturne Composition File- dans un éditeur de texte
print("PLAY NOCTURNE - Silent Voice ") print("=============================") import mido # importe bibliothèque Mido from mido import Message, MidiFile, MidiTrack # importe modules depuis Mido import time # importe le module Time Python mid = MidiFile() # Nous pouvons créer un nouveau fichier en appelant MidiFile track = MidiTrack() # sans l’argument du nom de fichier. Le fichier peut ensuite mid.tracks.append(track) # être enregistré à la fin en appelant la méthode save() # La partition convertie en valeurs numériques, noctn = notes et noctd = durée notes noctn = [74,71,67,69,71,67,69,74,71,67,69,67,71,69,74,72,71,69,67,69,72,71,69,74,71,69,67,69,71,67,74] noctd = [1.0,1.0,2.0,1.0,0.5,0.5,2.0,1.0,1.0,2.0,1.0,0.5,0.5,2.0,1.0,1.0,1.0,0.5,0.5,1.0,1.0,1.0,1.0,0.5,0.5,0.5,0.5,1.0,1.0,2.0,2.0] hauteur = [0,-12,0,+12,0] # choix des octaves à jouer, 12 = 1 octave et 0 = original x = 0 # initialisation index x lecture de gauche à droite liste "hauteur" for i in hauteur: # boucle octave à jouer par rapport aux notes d'origine delta = hauteur[x] # nb d'octaves à ajouter ou soustraire exprimé par tranche de 12 notes print(" => HAUTEUR =", delta,"notes...") # affiche nb notes en + ou - x = x +1 # incrémentation index x pour hauteur octave (de 0 à nb dans liste "hauteur") y = 0 # initialisation index y lecture de gauche à droite listes "noctn" et "noctd" for j in noctn: # boucle notes à jouer dans noctn (notes partition) track.append(Message('program_change', program=64, time=0)) # n. program=instrument track.append(Message('note_on', note = noctn[y] +delta, velocity = 100, time = 32)) print("Nocturne note #", noctn[y],"- Durée =", (noctd[y]), "- time =", int(256 *noctd[y])) track.append(Message('note_off', note = noctn[y] +delta, velocity = 67, time = int(256 *noctd[y]))) y = y +1 # incrémentation index y pour couple note/durée (de 0 à nb dans liste "noctn") track.append(Message('note_on', note = 62, velocity = 100, time = 32)) # touche final track.append(Message('note_off', note = 62, velocity = 67, time = 1024)) mid.save('MIDO_Write-Nocturne-Composition-File.mid') # enregistre le tout dans ce fichier Midi print("=> Fichier MIDI sauvegardé", mid, "...") # affiche info fichier Midi print("C'EST FINI !")
Dans le script ci-dessus, les listes noctn = [notes] et noctd = [durées] (lignes 12-13) sont la traduction en valeurs numériques de la petite partition musicale à jouer. Les notes utilisent directement la numérotation MIDI (0.. 127) et les durées ont été converties, croche = 0,5 s, noire = 1 s et blanche = 2 secondes. La liste hauteur = [valeurs multiple de 12] (ligne 14) permet de choisir l’octave auquel le morceau sera joué.
Nous réutilisons le principe de la boucle for/for imbriquée que nous avions vu dans l’article précédent. Le premier for (ligne 17) gère hauteur et le deuxième for (ligne 23) gère noctn/noctd, sachant qu’ici noctn/noctd doivent avoir exactement le même nombre d’éléments.
La sortie du script -MIDO Write Nocturne Composition File- dans une console Python
Python 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] on linux Type "help", "copyright", "credits" or "license()" for more information. >>> == RESTART: /home/joe/Python-Projects/Mido-Scripts/MIDO_PLAY-Nocturne-Composition.py == PLAY NOCTURNE - Silent Voice ============================= => HAUTEUR = 0 notes... Nocturne note # 74 - Durée = 1.0 - time = 256 Nocturne note # 71 - Durée = 1.0 - time = 256 Nocturne note # 67 - Durée = 2.0 - time = 512 ... ... ... Nocturne note # 74 - Durée = 2.0 - time = 512 => HAUTEUR = -12 notes... Nocturne note # 74 - Durée = 1.0 - time = 256 ... ... ... Nocturne note # 74 - Durée = 2.0 - time = 512 => HAUTEUR = 0 notes... Nocturne note # 74 - Durée = 1.0 - time = 256 ... ... ... Nocturne note # 74 - Durée = 2.0 - time = 512 => HAUTEUR = 12 notes... Nocturne note # 74 - Durée = 1.0 - time = 256 ... ... ... Nocturne note # 74 - Durée = 2.0 - time = 512 => HAUTEUR = 0 notes... Nocturne note # 74 - Durée = 1.0 - time = 256 Nocturne note # 71 - Durée = 1.0 - time = 256 Nocturne note # 67 - Durée = 2.0 - time = 512 Nocturne note # 69 - Durée = 1.0 - time = 256 Nocturne note # 71 - Durée = 0.5 - time = 128 Nocturne note # 67 - Durée = 0.5 - time = 128 ... ... ... Nocturne note # 67 - Durée = 2.0 - time = 512 Nocturne note # 74 - Durée = 2.0 - time = 512 => Fichier MIDI sauvegardé <midi file None type 1, 1 tracks, 467 messages> ... C'EST FINI ! >>>
• • • • • • • • • •
La sortie du script sur les hauts-parleurs de mon ordinateur pour une musique enfin structurée
Fichier MIDI ~ 2 Ko crée avec le script Python MidO -puis-
lu et converti directement par VLC en fichier audio OGG ~ 800 Ko
• • • • • • • • • •
♦ Focus sur MIDI-PLOT
Avant de nous intéresser aux Scripts MIDI-IN avec Python, MidO et RtMidi (5/6), nous allons explorer quelques possibilités ludiques de représentation graphique du MIDI sous Python.
⇒ Lire la suite : Scripts MIDI-PLOT avec Python, MidO et RtMidi (4/6)