Chapitre 10.5

Scripts

Les scripts font d’un coup ce qui prendrait des heures à la main : un accent sur chaque premier temps, supprimer les nuances répétées, transposer une portée, renommer les instruments de cent partitions, vérifier les tessitures. Ils s’écrivent en Lua 5.4, le langage des plug-ins JW Lua et RGP Lua de Finale.

Un script voit toute la partition (portées, mesures, notes, signes, textes, mise en page) et peut la modifier. Il travaille sur une copie : s’il se termine, toutes ses modifications deviennent une seule étape d’Annuler ; s’il s’arrête sur une erreur, la partition reste telle qu’elle était. Les scripts s’exécutent dans un environnement fermé : ils ne peuvent ni lire ni écrire de fichiers, ni utiliser le réseau, ni lancer de programmes.

Le menu Scripts

Chaque fichier .lua du dossier des scripts est un élément du menu Scripts ; les sous-dossiers sont des sous-menus. La première fois, Maestro y copie quelques exemples, dans le dossier Exemples : ouvrez-les dans un éditeur de texte pour voir comment ils sont faits. Certains refont les scripts les plus utilisés de la collection RGP Lua pour Finale (garder une note des accords, doubler à l’octave, échanger deux couches, supprimer les articulations des silences, compter les durées).

  • Scripts → Ouvrir le dossier des scripts l’affiche dans le Finder ou l’Explorateur de fichiers (sur Mac ~/Library/Application Support/Maestro/Script, sous Windows %LOCALAPPDATA%\Maestro\Script). Un fichier nouveau ou modifié est pris en compte tout de suite, sans redémarrer.
  • Un script agit sur la partition au premier plan : sur la sélection, si le script l’utilise, ou sur tout.
  • Exécuter à nouveau (⌥⌘U, sous Windows Ctrl+Alt+U) répète le dernier script avec les mêmes réponses aux paramètres.
  • Interrompre le script arrête un script trop long ; au bout de deux minutes, il s’arrête de toute façon.
  • Exécuter un script… ouvre n’importe quel fichier .lua, même hors du dossier.
  • Pendant que le script s’exécute, la partition reste libre ; mais si vous la modifiez entre-temps, les modifications du script ne sont pas appliquées (la console le dit) : vous ne perdez jamais ce que vous avez écrit.

Sur l’iPad, le bouton Scripts du pupitre exécute les scripts du dossier Script de Maestro (dans l’app Fichiers : Sur mon iPad → Maestro → Script), par exemple pour transposer un morceau pendant la lecture ; les modifications valent pour la lecture et le fichier ne change pas.

La console des scripts

Scripts → Console des scripts affiche ce que les scripts écrivent avec print, les messages de Maestro et les erreurs, avec le fichier et la ligne (« Accents.lua:12: Durée non valide… »). Elle s’ouvre d’elle-même après une erreur.

Dans la ligne du bas, vous pouvez écrire du Lua pour la partition au premier plan et appuyer sur Entrée : une expression affiche sa valeur (score.title, #score.staves), une instruction s’exécute (score.title = "Sonate"), avec une étape d’Annuler. Les flèches ↑ et ↓ rappellent les lignes précédentes.

Écrire un script

-- Un accent sur la première note de chaque mesure de la flûte,
-- et les notes au-dessus du do6 une octave plus bas
local flute = score.staves:find("Flûte")
for _, mesure in ipairs(flute.measures) do
  local premiere = mesure:layer(1)[1]
  if premiere and premiere.isNote then premiere:add("accent") end
end
for e in flute:entries() do
  for _, note in ipairs(e.notes) do
    if note.midi > 84 then note:transpose("-P8") end
  end
end
  • Les numéros commencent à 1, comme en Lua et comme les numéros de mesure : score.staves[1], mesure:layer(1).
  • Les propriétés se lisent et s’écrivent avec un point (e.duration = "8"), les méthodes s’appellent avec deux-points (e:add("accent")).
  • Les listes (score.staves, portee.measures, mesure:layer(1), e.notes, mesure.dynamics…) se comptent avec # et se parcourent avec ipairs ; les itérateurs score:entries(), portee:entries(), mesure:entries() donnent toutes les notes, accords et silences.
Objet Ce que c’est Quelques propriétés et méthodes
score la partition title, composer, tempo, staves, measures, pageFormat ; insertMeasures, deleteMeasures, setKey, setTime, transpose, addStaff
portée score.staves[n] name, abbreviatedName, clef, measures ; setClef, move, delete
mesure portee.measures[n] number, clef, dynamics, hairpins, texts, chords ; layer(n), append, clear, fillWithRests
mesure de la partition score.measures[n] time, key ; setTime, setKey
note, accord ou silence mesure:layer(1)[n] duration, isRest, isChord, tick, tied, articulations, notes ; add, remove, transpose, setNotes, delete
hauteur e.notes[n] name, step, alter, octave, midi ; transpose, delete
signes mesure.dynamics[n], score.slurs[n]… les champs du signe (marking, tick…) ; delete ; dans les listes add({ … })

Les durées s’écrivent "w", "h", "q", "8", "16", "32" avec les points ("q.") ; les hauteurs "C4" (do central), "F#5", "Bb3", ou aussi "Sol4" ; les intervalles "P8", "-m3", "M2", "A4" ; les articulations "accent", "staccato", "tenuto", "fermata"….

En plus des propriétés du tableau, chaque objet atteint chaque champ de la partition par le nom qu’il a dans le fichier .maestro : e.hidden = true, e:set("pitches.1.notehead", "diamond"), portee:raw() (tout l’objet sous forme de table) et :replace(table), :fields(). Un nom erroné donne une erreur au lieu de passer en silence. Le script lui-même donne la liste complète : maestro.schema() et maestro.schema("entry").

Parcourir la partition

Les fonctions eachentry, eachcell, eachstaff et each (les mêmes que dans RGP Lua) parcourent une région : sans arguments, la sélection ou, s’il n’y en a pas, toute la partition.

-- Un staccato sur toutes les croches de la sélection (ou de la partition)
for e in eachentry() do
  if e.isNote and e.duration == "8" then e:add("staccato") end
end
Fonction Ce qu’elle donne
eachentry(region, couche) notes, accords et silences, portée par portée et mesure par mesure ; couche de 1 à 4 (toutes par défaut)
eachcell(region) chaque mesure de chaque portée : for mesure, portee in eachcell() do … end
eachstaff(region) les portées
each(liste), eachbackwards(liste) les éléments d’une liste, ou les notes d’un accord (each(e)) ; à rebours, on peut supprimer en parcourant
pairsbykeys(table) les paires d’une table dans l’ordre des clés

La région peut être selection (seulement la sélection : rien s’il n’y en a pas), score, une portée, une mesure (score.staves[1].measures[4] ou score.measures[4]) ou une table comme { staves = {1, 2}, measures = {5, 8} } : un nombre, ou le premier et le dernier ; ce qui manque vaut tout. Il y a aussi eachentrysaved, pour ceux qui viennent de Finale, mais c’est la même chose que eachentry : dans Maestro, les modifications s’appliquent tout de suite.

Les commandes de l’éditeur

editor.perform fait ce que tu ferais à la main avec les palettes Articulations, Expressions, Lignes, N-olets, Reprises et Mise en page : liaisons, soufflets, lignes d’octave, textes, fins alternatives, sauts de système… Elle agit sur une région, comme une sélection : par défaut la sélection, ou une entrée, une liste d’entrées, une mesure, une portée ou { staves = …, measures = … }. Les questions de la fenêtre de la commande trouvent leur réponse dans les options.

editor.perform("slur", { staves = 1, measures = {1, 2} })            -- liaison de la première à la dernière note
editor.perform("crescendo", score.staves[1].measures[5])
editor.perform("ending", { measures = 8, numbers = {1} })            -- première fin
editor.perform("tuplet", e, { ratio = "5:4" })                      -- quintolet à partir de l'entrée e
editor.perform("text", mesure, { text = "dolce", kind = "expression" })
editor.perform("systemBreak", { measures = 9 })                      -- nouveau système à la mesure 9

editor.commands() donne la liste des commandes avec leurs options. Les articulations s’ajoutent ou s’enlèvent, comme dans la palette. Si l’éditeur refuse la commande (« Sélectionne au moins deux notes… »), le script s’arrête avec une erreur à cette ligne ; ces commandes restent elles aussi dans l’unique étape d’annulation du script. Chaque commande remet la partition en page : dans les longues boucles, mieux vaut une commande sur une grande région qu’une par note.

Nom, raccourci et paramètres

Un bloc de métadonnées facultatif en tête du script donne le nom dans le menu (en plusieurs langues), une description, un raccourci et les paramètres à demander avant de commencer :

--[[ maestro
name = { fr = "Transposer", en = "Transpose" }
description = { fr = "Transpose la sélection de l’intervalle choisi." }
shortcut = "mod+alt+shift+Y"   -- mod = ⌘ sur Mac, Ctrl sous Windows ; ou "F9"
needsSelection = true
params = {
  { name = "interval", type = "text", default = "M2", label = { fr = "Intervalle" } },
  { name = "fois", type = "number", default = 1, min = 1, max = 4 },
}
]]
for e in selection.entries() do e:transpose(params.interval) end

Maestro affiche un formulaire avec les paramètres (la fois suivante, il propose les réponses précédentes) et le script les trouve dans params. Les types sont number, text, boolean et choice (avec choices = { … }). Un raccourci déjà utilisé par Maestro n’est pas attribué. require("nom") charge les modules du dossier lib du dossier des scripts.

Sur un dossier entier

Scripts → Exécuter sur un dossier… applique un script à toutes les partitions d’un dossier (y compris dans les sous-dossiers) : Maestro, Finale, MusicXML, MuseScore et MIDI. Dans le formulaire, vous choisissez les paramètres du script et le résultat : seulement le rapport, ou les fichiers enregistrés dans un autre dossier comme documents Maestro, MusicXML, MusicXML compressé, Finale (.musx) ou MIDI. Les fichiers d’origine ne changent jamais. La console affiche le résultat fichier par fichier.

Dans le terminal : maestro-script

La commande maestro-script fait la même chose sans fenêtres, pour les traitements par lots et pour d’autres programmes :

maestro-script run Accents.lua Partitions/ --recursive --out Convertis/ --format musicxml
maestro-script eval 'score.title' Partitions/
maestro-script run Transposer.lua morceau.musx -p interval=-M2 --out Sortie/ --json
maestro-script help

Sur Mac, elle se trouve dans Maestro : Scripts → Installer la commande maestro-script… la rend disponible dans le Terminal. Sous Windows, c’est maestro-script.exe dans le dossier de Maestro (Scripts → La commande maestro-script… indique où). Elle n’existe pas dans la version du Mac App Store.

Avec un assistant (Claude et d’autres)

maestro-script mcp est un serveur MCP : un assistant comme Claude peut ouvrir vos partitions, écrire et exécuter des scripts, regarder les pages et enregistrer le résultat. Il ne lit et n’écrit que dans les dossiers indiqués avec --root ; il n’écrase jamais l’original ni, sans --allow-overwrite, un fichier existant.

Avec Claude Code, sur Mac :

claude mcp add maestro -- /Applications/Maestro.app/Contents/MacOS/maestro-script mcp --root ~/Partitions

Avec Claude Desktop, dans le fichier de configuration des serveurs MCP :

{
  "mcpServers": {
    "maestro": {
      "command": "/Applications/Maestro.app/Contents/MacOS/maestro-script",
      "args": ["mcp", "--root", "/Users/moi/Partitions"]
    }
  }
}

Sous Windows, la commande est %LOCALAPPDATA%\Programs\Maestro\maestro-script.exe. Il suffit ensuite de demander : « ouvre Menuet.musx, mets un accent sur chaque premier temps de la flûte, montre-moi la première page et enregistre-le sous Menuet-accents.maestro ».

Sur les partitions ouvertes dans Maestro

Un assistant peut aussi travailler sur les partitions ouvertes dans l’app pendant que vous les regardez. C’est désactivé par défaut : activez-le avec Scripts → Autoriser les assistants à modifier les partitions ouvertes et connectez l’assistant avec maestro-script mcp --app ; avec Claude Code, sur Mac :

claude mcp add maestro-app -- /Applications/Maestro.app/Contents/MacOS/maestro-script mcp --app

L’assistant voit les partitions ouvertes et votre sélection ; chacun de ses scripts est une étape d’Annuler nommée « Assistant » et apparaît dans la console des scripts ; c’est vous qui enregistrez les fichiers. La connexion reste sur votre ordinateur et votre compte utilisateur, sans réseau.

Écrire des scripts avec un éditeur

Le dossier des scripts contient aussi la description de l’interface pour les éditeurs (.maestro/maestro.d.lua et .luarc.json) : ouvrez-le dans Visual Studio Code avec l’extension Lua pour avoir la complétion des propriétés et des méthodes et les vérifications pendant la saisie.

Les données d’origine de Finale

Une partition ouverte depuis un fichier Finale (.musx ou .mus) garde, tant qu’elle reste ouverte, les données d’origine du fichier : les scripts les lisent avec finale, avec les noms de l’EnigmaXML (les mêmes que musxdom et le PDK de Finale). Elles servent à récupérer ce que l’importation n’apporte pas dans Maestro, ou à comparer.

local ms = finale.others("measSpec", 1)[1]          -- la mesure 1 telle que Finale l’écrit
print(ms.beats, ms.divbeat, ms.width)
for e in score:entries() do
  local n = finale.entnum(e)                         -- l’entrée Finale d’où elle vient
  if n then
    local original = finale.entry(n)
    -- … par exemple reprendre une information que l’importation ne lit pas
  end
end
  • finale.others(tag, cmper, inci), finale.details(tag, cmper1, cmper2, inci), finale.texts(tag, numero) : les enregistrements (les arguments après le nom sont des filtres facultatifs) ; finale.entry(entnum), finale.header(), finale.options(tag), finale.tags() (les noms présents).
  • Un enregistrement est une table : _tag est le nom, les attributs et les éléments sont des champs ; un élément vide vaut true, un élément répété est une séquence (finale.list(x) donne toujours une séquence).
  • finale.entryFor(entnum) et finale.entnum(entree), finale.staffFor(cmper) et finale.staffCmper(portee) relient les objets de Finale et ceux de Maestro.
  • C’est en lecture seule et non enregistré : après Enregistrer en .maestro et une nouvelle ouverture, finale n’existe plus. Pour les .mus, il contient les enregistrements que Maestro sait lire.

Pour ceux qui viennent de RGP Lua

Le langage est le même, les objets sont ceux de Maestro :

RGP Lua (Finale) Maestro
plugindef() le bloc --[[ maestro … ]] en tête du script
finenv.Region() selection ; sans région, les fonctions de parcours prennent la sélection ou toute la partition
for e in eachentrysaved(finenv.Region()) for e in eachentry() (aussi eachentrysaved, eachcell, eachstaff, each, pairsbykeys)
finale.FCStaves, FCStaff score.staves, score.staves[n]
finale.FCMeasures, FCMeasure score.measures[n] (chiffrage et armure), portee.measures[n] (la musique)
FCNoteEntryLayer, FCNoteEntry mesure:layer(n), mesure:layer(n)[i]
FCNote e.notes[i]
entry.Duration (EDU, noire 1024) e.duration ("q") ou e.ticks (noire 960)
FCExpression, FCSmartShape mesure.dynamics, mesure.texts, score.slurs, mesure.hairpins…
entry:Save(), measure:Save() inutile : les modifications s’appliquent tout de suite (et s’annulent ensemble)