Kapitel 10.5

Skripte

Skripte erledigen in einem Zug, was von Hand Stunden dauern würde: ein Akzent auf jeder Zählzeit eins, wiederholte Dynamik entfernen, eine Notenzeile transponieren, die Instrumente von hundert Partituren umbenennen, Tonumfänge prüfen. Sie werden in Lua 5.4 geschrieben, der Sprache der Finale-Plug-ins JW Lua und RGP Lua.

Ein Skript sieht die ganze Partitur (Notenzeilen, Takte, Noten, Zeichen, Texte, Layout) und kann sie ändern. Es arbeitet an einer Kopie: Wenn es fertig wird, werden alle seine Änderungen ein einziger Rückgängig-Schritt; bricht es mit einem Fehler ab, bleibt die Partitur, wie sie war. Skripte laufen in einer geschlossenen Umgebung: Sie können keine Dateien lesen oder schreiben, das Netz nicht nutzen und keine Programme starten.

Das Menü Skripte

Jede .lua-Datei im Skriptordner ist ein Eintrag im Menü Skripte; Unterordner sind Untermenüs. Beim ersten Mal kopiert Maestro einige Beispiele hinein, in den Ordner Beispiele: Öffne sie in einem Texteditor, um zu sehen, wie sie gemacht sind. Einige bilden die meistgenutzten Skripte der RGP-Lua-Sammlung für Finale nach (eine Note der Akkorde behalten, in der Oktave verdoppeln, zwei Ebenen tauschen, Artikulationen von Pausen entfernen, Notenwerte zählen).

  • Skripte → Skriptordner öffnen zeigt ihn im Finder oder im Explorer (auf dem Mac ~/Library/Application Support/Maestro/Script, unter Windows %LOCALAPPDATA%\Maestro\Script). Eine neue oder geänderte Datei gilt sofort, ohne Neustart.
  • Ein Skript arbeitet mit der vorderen Partitur: mit der Auswahl, wenn das Skript sie verwendet, oder mit allem.
  • Erneut ausführen (⌥⌘U, unter Windows Strg+Alt+U) wiederholt das letzte Skript mit denselben Antworten auf die Parameter.
  • Skript abbrechen hält ein Skript an, das zu lange braucht; nach zwei Minuten hält es ohnehin an.
  • Skript ausführen… öffnet eine beliebige .lua-Datei, auch außerhalb des Ordners.
  • Während das Skript läuft, bleibt die Partitur frei; änderst du sie aber in der Zwischenzeit, werden die Änderungen des Skripts nicht übernommen (die Konsole sagt es), damit nichts verloren geht, was du geschrieben hast.

Auf dem iPad führt die Taste Skripte des Notenpults die Skripte im Ordner Script von Maestro aus (in der App Dateien: Auf meinem iPad → Maestro → Script), etwa um ein Stück beim Lesen zu transponieren; die Änderungen gelten nur zum Lesen, die Datei ändert sich nicht.

Die Skriptkonsole

Skripte → Skriptkonsole zeigt, was Skripte mit print schreiben, die Meldungen von Maestro und die Fehler mit Datei und Zeile („Akzente.lua:12: Ungültiger Notenwert…“). Nach einem Fehler öffnet sie sich von selbst.

In der Zeile unten kannst du Lua für die vordere Partitur schreiben und Eingabe drücken: Ein Ausdruck zeigt seinen Wert (score.title, #score.staves), eine Anweisung wird ausgeführt (score.title = "Sonate"), mit einem Rückgängig-Schritt. Die Pfeile ↑ und ↓ holen frühere Zeilen zurück.

Ein Skript schreiben

-- Ein Akzent auf der ersten Note jedes Takts der Flöte,
-- und die Noten über C6 eine Oktave tiefer
local floete = score.staves:find("Flöte")
for _, takt in ipairs(floete.measures) do
  local erste = takt:layer(1)[1]
  if erste and erste.isNote then erste:add("accent") end
end
for e in floete:entries() do
  for _, note in ipairs(e.notes) do
    if note.midi > 84 then note:transpose("-P8") end
  end
end
  • Zahlen beginnen bei 1, wie in Lua und wie die Taktnummern: score.staves[1], takt:layer(1).
  • Eigenschaften liest und schreibt man mit Punkt (e.duration = "8"), Methoden ruft man mit Doppelpunkt auf (e:add("accent")).
  • Listen (score.staves, notenzeile.measures, takt:layer(1), e.notes, takt.dynamics…) zählt man mit # und durchläuft sie mit ipairs; die Iteratoren score:entries(), notenzeile:entries(), takt:entries() liefern alle Noten, Akkorde und Pausen.
Objekt Was es ist Einige Eigenschaften und Methoden
score die Partitur title, composer, tempo, staves, measures, pageFormat; insertMeasures, deleteMeasures, setKey, setTime, transpose, addStaff
Notenzeile score.staves[n] name, abbreviatedName, clef, measures; setClef, move, delete
Takt notenzeile.measures[n] number, clef, dynamics, hairpins, texts, chords; layer(n), append, clear, fillWithRests
Takt der Partitur score.measures[n] time, key; setTime, setKey
Note, Akkord oder Pause takt:layer(1)[n] duration, isRest, isChord, tick, tied, articulations, notes; add, remove, transpose, setNotes, delete
Tonhöhe e.notes[n] name, step, alter, octave, midi; transpose, delete
Zeichen takt.dynamics[n], score.slurs[n]… die Felder des Zeichens (marking, tick…); delete; in den Listen add({ … })

Notenwerte schreibt man "w", "h", "q", "8", "16", "32" mit Punkten ("q."); Tonhöhen "C4" (eingestrichenes C), "F#5", "Bb3" (B ist "Bb", H ist "B"); Intervalle "P8", "-m3", "M2", "A4"; Artikulationen "accent", "staccato", "tenuto", "fermata"….

Neben den Eigenschaften der Tabelle erreicht jedes Objekt jedes Feld der Partitur unter dem Namen, den es in der .maestro-Datei hat: e.hidden = true, e:set("pitches.1.notehead", "diamond"), notenzeile:raw() (das ganze Objekt als Tabelle) und :replace(tabelle), :fields(). Ein falscher Name gibt einen Fehler, statt still durchzugehen. Die vollständige Liste liefert das Skript selbst: maestro.schema() und maestro.schema("entry").

Die Partitur durchlaufen

Die Funktionen eachentry, eachcell, eachstaff und each (dieselben wie in RGP Lua) durchlaufen einen Bereich: ohne Argumente die Auswahl oder, wenn es keine gibt, die ganze Partitur.

-- Ein Staccato auf jeder Achtelnote der Auswahl (oder der Partitur)
for e in eachentry() do
  if e.isNote and e.duration == "8" then e:add("staccato") end
end
Funktion Was sie liefert
eachentry(bereich, ebene) Noten, Akkorde und Pausen, Notenzeile für Notenzeile und Takt für Takt; ebene von 1 bis 4 (standardmäßig alle)
eachcell(bereich) jeden Takt jeder Notenzeile: for takt, notenzeile in eachcell() do … end
eachstaff(bereich) die Notenzeilen
each(liste), eachbackwards(liste) die Elemente einer Liste oder die Noten eines Akkords (each(e)); rückwärts kann man beim Durchlaufen löschen
pairsbykeys(tabelle) die Paare einer Tabelle nach Schlüsseln sortiert

Der Bereich kann selection sein (nur die Auswahl: nichts, wenn es keine gibt), score, eine Notenzeile, ein Takt (score.staves[1].measures[4] oder score.measures[4]) oder eine Tabelle wie { staves = {1, 2}, measures = {5, 8} }: eine Zahl oder die erste und die letzte; was fehlt, bedeutet alle. Es gibt auch eachentrysaved, für Umsteiger von Finale, aber es ist dasselbe wie eachentry: In Maestro gelten Änderungen sofort.

Die Befehle des Editors

editor.perform tut, was du von Hand mit den Paletten Artikulationen, Ausdrücke, Linien, N-tolen, Wiederholungen und Layout tun würdest: Bögen, Gabeln, Oktavlinien, Texte, Volten, Systemumbrüche… Der Befehl wirkt auf einen Bereich, wie eine Auswahl: standardmäßig die Auswahl, sonst ein Eintrag, eine Liste von Einträgen, ein Takt, ein System oder { staves = …, measures = … }. Die Fragen des Dialogs beantworten die Optionen.

editor.perform("slur", { staves = 1, measures = {1, 2} })            -- Bogen von der ersten bis zur letzten Note
editor.perform("crescendo", score.staves[1].measures[5])
editor.perform("ending", { measures = 8, numbers = {1} })            -- erste Volta
editor.perform("tuplet", e, { ratio = "5:4" })                      -- Quintole ab dem Eintrag e
editor.perform("text", takt, { text = "dolce", kind = "expression" })
editor.perform("systemBreak", { measures = 9 })                      -- neues System ab Takt 9

editor.commands() listet die Befehle mit ihren Optionen. Artikulationen werden gesetzt oder entfernt, wie in der Palette. Lehnt der Editor den Befehl ab („Wähle mindestens zwei Noten…“), hält das Skript mit einem Fehler in dieser Zeile an; auch diese Befehle bleiben im einzigen Rückgängig-Schritt des Skripts. Jeder Befehl setzt die Partitur neu: in langen Schleifen besser ein Befehl auf einen großen Bereich als einer pro Note.

Name, Tastenkürzel und Parameter

Ein optionaler Metadatenblock am Anfang des Skripts gibt den Namen im Menü (in mehreren Sprachen), eine Beschreibung, ein Tastenkürzel und die Parameter, die vor dem Start abgefragt werden:

--[[ maestro
name = { de = "Transponieren", en = "Transpose" }
description = { de = "Transponiert die Auswahl um das gewählte Intervall." }
shortcut = "mod+alt+shift+Y"   -- mod = ⌘ auf dem Mac, Strg unter Windows; oder "F9"
needsSelection = true
params = {
  { name = "interval", type = "text", default = "M2", label = { de = "Intervall" } },
  { name = "mal", type = "number", default = 1, min = 1, max = 4 },
}
]]
for e in selection.entries() do e:transpose(params.interval) end

Maestro zeigt ein Formular mit den Parametern (beim nächsten Mal schlägt es die vorigen Antworten vor), und das Skript findet sie in params. Die Typen sind number, text, boolean und choice (mit choices = { … }). Ein Tastenkürzel, das Maestro schon benutzt, wird nicht vergeben. require("name") lädt Module aus dem Ordner lib des Skriptordners.

Für einen ganzen Ordner

Skripte → Auf einen Ordner anwenden… wendet ein Skript auf alle Partituren eines Ordners an (auch in Unterordnern): Maestro, Finale, MusicXML, MuseScore und MIDI. Im Formular wählst du die Parameter des Skripts und das Ergebnis: nur den Bericht oder die Dateien in einem anderen Ordner gespeichert, als Maestro-Dokumente, MusicXML, komprimiertes MusicXML, Finale (.musx) oder MIDI. Die Originaldateien ändern sich nie. Die Konsole zeigt das Ergebnis Datei für Datei.

Im Terminal: maestro-script

Der Befehl maestro-script macht dasselbe ohne Fenster, für die Stapelverarbeitung und für andere Programme:

maestro-script run Akzente.lua Partituren/ --recursive --out Konvertiert/ --format musicxml
maestro-script eval 'score.title' Partituren/
maestro-script run Transponieren.lua stueck.musx -p interval=-M2 --out Ausgabe/ --json
maestro-script help

Auf dem Mac steckt er in Maestro: Skripte → Befehl maestro-script installieren… macht ihn im Terminal verfügbar. Unter Windows ist es maestro-script.exe im Ordner von Maestro (Skripte → Der Befehl maestro-script… sagt, wo). In der Version aus dem Mac App Store gibt es ihn nicht.

Mit einem Assistenten (Claude und andere)

maestro-script mcp ist ein MCP-Server: Ein Assistent wie Claude kann deine Partituren öffnen, Skripte schreiben und ausführen, die Seiten ansehen und das Ergebnis speichern. Er liest und schreibt nur in den Ordnern, die du mit --root angibst; er überschreibt nie das Original und ohne --allow-overwrite keine vorhandene Datei.

Mit Claude Code auf dem Mac:

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

Mit Claude Desktop in der Konfigurationsdatei der MCP-Server:

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

Unter Windows lautet der Befehl %LOCALAPPDATA%\Programs\Maestro\maestro-script.exe. Dann genügt eine Bitte: „Öffne Menuett.musx, setze einen Akzent auf jede Zählzeit eins der Flöte, zeig mir die erste Seite und speichere es als Menuett-Akzente.maestro.“

An den in Maestro offenen Partituren

Ein Assistent kann auch an den in der App offenen Partituren arbeiten, während du sie ansiehst. Das ist standardmäßig aus: Schalte es mit Skripte → Assistenten die Bearbeitung offener Partituren erlauben ein und verbinde den Assistenten mit maestro-script mcp --app; mit Claude Code auf dem Mac:

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

Der Assistent sieht die offenen Partituren und deine Auswahl; jedes seiner Skripte ist ein Rückgängig-Schritt namens „Assistent“ und erscheint in der Skriptkonsole; die Dateien sicherst du. Die Verbindung bleibt auf deinem Computer und bei deinem Benutzer, ohne Netzwerk.

Skripte mit einem Editor schreiben

Der Skriptordner enthält auch die Beschreibung der Schnittstelle für Editoren (.maestro/maestro.d.lua und .luarc.json): Öffne ihn in Visual Studio Code mit der Lua-Erweiterung, dann hast du Autovervollständigung für Eigenschaften und Methoden und Prüfungen beim Schreiben.

Die Originaldaten von Finale

Eine aus einer Finale-Datei (.musx oder .mus) geöffnete Partitur behält, solange sie offen ist, die Originaldaten der Datei: Skripte lesen sie über finale, mit den Namen des EnigmaXML (dieselben wie in musxdom und im PDK von Finale). Damit holst du zurück, was der Import nicht nach Maestro bringt, oder vergleichst.

local ms = finale.others("measSpec", 1)[1]          -- Takt 1, wie Finale ihn schreibt
print(ms.beats, ms.divbeat, ms.width)
for e in score:entries() do
  local n = finale.entnum(e)                         -- der Finale-Eintrag, aus dem er stammt
  if n then
    local original = finale.entry(n)
    -- … etwa etwas übernehmen, das der Import nicht liest
  end
end
  • finale.others(tag, cmper, inci), finale.details(tag, cmper1, cmper2, inci), finale.texts(tag, nummer): die Datensätze (die Argumente nach dem Namen sind optionale Filter); finale.entry(entnum), finale.header(), finale.options(tag), finale.tags() (die vorhandenen Namen).
  • Ein Datensatz ist eine Tabelle: _tag ist der Name, Attribute und Unterelemente sind Felder; ein leeres Element ist true, ein wiederholtes eine Folge (finale.list(x) liefert immer eine Folge).
  • finale.entryFor(entnum) und finale.entnum(eintrag), finale.staffFor(cmper) und finale.staffCmper(notenzeile) verbinden die Objekte von Finale und Maestro.
  • Nur lesbar und nicht gespeichert: Nach Sichern als .maestro und erneutem Öffnen gibt es finale nicht mehr. Bei .mus-Dateien enthält es die Datensätze, die Maestro lesen kann.

Für Umsteiger von RGP Lua

Die Sprache ist dieselbe, die Objekte sind die von Maestro:

RGP Lua (Finale) Maestro
plugindef() der Block --[[ maestro … ]] am Anfang des Skripts
finenv.Region() selection; die Funktionen zum Durchlaufen nehmen ohne Bereich die Auswahl oder die ganze Partitur
for e in eachentrysaved(finenv.Region()) for e in eachentry() (auch eachentrysaved, eachcell, eachstaff, each, pairsbykeys)
finale.FCStaves, FCStaff score.staves, score.staves[n]
finale.FCMeasures, FCMeasure score.measures[n] (Takt- und Tonartangabe), notenzeile.measures[n] (die Musik)
FCNoteEntryLayer, FCNoteEntry takt:layer(n), takt:layer(n)[i]
FCNote e.notes[i]
entry.Duration (EDU, Viertel 1024) e.duration ("q") oder e.ticks (Viertel 960)
FCExpression, FCSmartShape takt.dynamics, takt.texts, score.slurs, takt.hairpins…
entry:Save(), measure:Save() nicht nötig: Änderungen gelten sofort (und werden gemeinsam rückgängig gemacht)