Manipuler des fichiers MIDI par script groovy
On souhaite parfois effectuer des manipulations de fichiers MIDI pour effectuer différentes opérations (transposition, comptage de notes, voir d'adéquation d'un morceau par rapport à une gamme d'instrument).
Le logiciel propose un ensemble de fonctions facilitant la manipulation des fichiers MIDI en quelques lignes de script (donc accessible par un non développeur). Ces scripts peuvent également être stockés dans la définition de l'instrument pour proposer des imports MIDI spécifiques.
Sommaire
Filtrage du contenu d'un fichier midi
Création d'un script d'import MIDI pour un instrument
Introduction
Le logiciel est implémenté en Java. Java propose des objets standards de manipulation de fichiers midi, cependant leur modification n'est pas très aisée du fait de la présence d'"Evènements" pouvant être liés. Par exemple les notes sont décrites par un évènement NoteOn puis un second évènement NoteOff. Les dates ou timestamp de début de notes ne sont pas codés en absolu (c'est à dire par rapport au début du morceau) mais en relatif. Chaque évènement est référencé dans le temps par rapport à l'évènement précédent (tick).
Des objets plus simples ont été mis en place pour permettre une manipulation plus simple des fichiers midi, du filtrage et de l'interprétation des éléments contenus dans le fichier. Des exemples suivent sur l'utilisation de ces objets, avec des exemples de mise en oeuvre.
Lecture d'un fichier MIDI en 5 Lignes
import groovy.aprint.midi.*
use (MidiCategory, MidiFileCategory)
{
def mf = new File("C:\\Users\\use\\Dropbox\\Midi MM\\LeBateauDeTahiti.mid").load()
}
Le Résultat de la console affiche la collection d'évènements MIDI lus dans le fichier
Filtrage d'un fichier Midi
Le bout de script suivant lit un fichier midi et filtre les notes du canal numéro 1
import groovy.aprint.midi.*
use (MidiCategory, MidiFileCategory)
{
def mf = new File("C:\\Users\\use\\Dropbox\\Midi MM\\LeBateauDeTahiti.mid").load()
// filtrage des évènements associé au canal (1), cad 0 si l'on commence la numérotation à 0
def resultatFiltrage = mf.filterChannel(0)
// Ecriture dans un fichier midi de sortie
resultatFiltrage.save(new File("c:\\temp\\test.mid"))
}
Le résultat est un fichier MDI test.mid ne contenant que les évènements associés au canal 1. La fonction filterChannel filtre les évènements associés au canal concerné.
D'autres fonctions de filtrage sont disponibles :
Fonction / Description | Exemple |
---|---|
filterMidiTrack Filtre les évènements MIDI associés à une piste dans le fichier midi |
|
filter
filtre générique pour filtrer le contenu avec une fonction utilisateur. Ceci permet de se faire sa propre fonction de filtrage. |
|
Fonctions d'évaluation du fichier midi
Il est parfois intéressant de pouvoir évaluer les notes présentes dans le fichier MIDI pour effectuer une comparaison avec la gamme et permettre l'évaluation de transposition.
Des fonctions ont été ajoutées pour cela.
listDistinctNotes()
Cette fonction permet de lister les notes distinctes d'un fichier midi
import groovy.aprint.midi.*
use (MidiCategory, MidiFileCategory)
{
def mf = new File("C:\\Users\\use\\Dropbox\\Midi MM\\LeBateauDeTahiti.mid").load()
// filtrage des évènements associé au canal (1), cad 0 si l'on commence la numérotation à 0
def notes = mf.listDistinctNotes()
notes.each { note ->
println note
}
}
Le résultat affiche ceci :
>> start script execution
E3
G3
A3
C4
D4
E4
A4
B4
C5
D5
E5
F#5
G5
A5
A#5
B5
C6
C#6
D6
E6
F#6
G6
A6
A#6
B6
C7
C#7
D7
E7
Ces notes sont les notes rencontrées dans le fichier MIDI
int[] listTracks()
Cette fonction permet de lister les pistes d'un fichier MIDI
import groovy.aprint.midi.*
use (MidiCategory, MidiFileCategory)
{
def mf = new File("C:\\Users\\use\\Dropbox\\Midi MM\\LeBateauDeTahiti.mid").load()
// filtrage des évènements associé au canal (1), cad 0 si l'on commence la numérotation à 0
def tracks = mf.listTracks()
tracks.each { track ->
println track
}
}
Le résultat affiché est le suivant :
>> start script execution
1
2
3
Script executed in 0: 0:846
Les pistes sont identifiées par rapport à un numéro, c'est le numéro présent dans le fichier MIDI. Le nom de la piste est ensuite contenu dans un évènement associé à la piste.
countDistinctNotes()
Cette fonction est particulièrement intéressante pour analyser la répartition des notes dans un ensemble de notes. Cette fonction retourne par note, le nombre d'occurence de la note dans le groupe.
import groovy.aprint.midi.*
use (MidiCategory, MidiFileCategory)
{
def mf = new File("C:\\Users\\use\\Dropbox\\Midi MM\\LeBateauDeTahiti.mid").load()
// filtrage des évènements associé au canal (1), cad 0 si l'on commence la numérotation à 0
def dnotes = mf.countDistinctNotes()
dnotes.each { note, nombre ->
println "" + note + " - " + nombre + " fois"
}
}
Le résultat est le suivant :
>> start script execution
E6 - 61 fois
C5 - 190 fois
E5 - 69 fois
C6 - 103 fois
G5 - 82 fois
E7 - 29 fois
G6 - 151 fois
D7 - 89 fois
F#5 - 137 fois
D5 - 336 fois
A6 - 101 fois
D6 - 182 fois
A5 - 84 fois
F#6 - 114 fois
C7 - 70 fois
B6 - 109 fois
B5 - 93 fois
A#6 - 5 fois
A#5 - 1 fois
D4 - 110 fois
G3 - 82 fois
B4 - 165 fois
E4 - 5 fois
C#6 - 7 fois
C#7 - 6 fois
A3 - 13 fois
C4 - 20 fois
E3 - 124 fois
A4 - 26 fois
Outil de transformation de fichiers MIDI en carton
Il est possible à partir du fichier midi d'effectuer une transformation en carton, la transformation commence par la lecture du fichier midi, en fonction de la gamme et de la capacité de l'instrument, certaines notes, percussions ou registres sont convertis en trous.
L'association entre les deux peut être réalisé à la main en script, en parcourant les notes du fichier midi et en créant les trous dans le carton virtuel. Une autre façon plus évoluée et décrite ici permet de créer des associations entre les notes midi et les pistes du carton, les trous sont alors créés automatiquement en fonction des temps et des longueurs des notes dans le fichier MIDI.
// exemple simple de script d'import de fichier midi
import groovy.aprint.midi.*
import groovy.aprint.transform.*
use (MidiCategory, MidiFileCategory, NoteCategory, ScaleCategory)
{
def mf = new File("C:\\Users\\use\\Dropbox\\Midi MM\\LeBateauDeTahiti.mid").load()
def ins = services.repository.getInstrument("27 / 29 Flutes")
def scale = ins.scale
def sh = scale.helper
def t = mf.transformFor(scale)
// on associe la note "D5" à la piste de la gamme ayant la note D5
t.map("D5".note, sh.find("D5"))
def result = t.doConvert()
services.newVirtualBook(result.virtualbook, ins)
}