Capítulo 10.5

Scripts

Os scripts fazem de uma vez o que levaria horas à mão: um acento em cada primeiro tempo, remover dinâmicas repetidas, transpor uma pauta, renomear os instrumentos de cem partituras, verificar as extensões. Eles são escritos em Lua 5.4, a linguagem dos plug-ins JW Lua e RGP Lua do Finale.

Um script vê a partitura inteira (pautas, compassos, notas, sinais, textos, diagramação) e pode alterá-la. Ele trabalha numa cópia: se termina, todas as suas alterações viram um único passo de Desfazer; se para com um erro, a partitura fica como estava. Os scripts rodam num ambiente fechado: não podem ler nem gravar arquivos, usar a rede ou abrir programas.

O menu Scripts

Cada arquivo .lua da pasta de scripts é um item do menu Scripts; as subpastas são submenus. Na primeira vez, o Maestro copia ali alguns exemplos, na pasta Exemplos: abra-os num editor de texto para ver como funcionam. Alguns refazem os scripts mais usados da coleção RGP Lua para o Finale (manter uma nota dos acordes, dobrar à oitava, trocar duas camadas, remover articulações das pausas, contar as durações).

  • Scripts → Abrir a Pasta de Scripts a mostra no Finder ou no Explorador de Arquivos (no Mac é ~/Library/Application Support/Maestro/Script, no Windows %LOCALAPPDATA%\Maestro\Script). Um arquivo novo ou alterado funciona na hora, sem reiniciar.
  • Um script trabalha na partitura que está na frente: na Seleção, se o script a usa, ou em tudo.
  • Executar Novamente (⌥⌘U, no Windows Ctrl+Alt+U) repete o último script com as mesmas respostas aos seus parâmetros.
  • Interromper o Script para um script que demora demais; depois de dois minutos ele para de qualquer jeito.
  • Executar um Script… abre qualquer arquivo .lua, também fora da pasta.
  • Enquanto o script roda, a partitura fica livre; mas se você a alterar nesse meio-tempo, as alterações do script não são aplicadas (o console avisa), para você nunca perder o que escreveu.

No iPad, o botão Scripts da estante executa os scripts da pasta Script do Maestro (no app Arquivos: No Meu iPad → Maestro → Script), por exemplo para transpor uma peça enquanto você a lê; as alterações são só para a leitura e o arquivo não muda.

O console de scripts

Scripts → Console de Scripts mostra o que os scripts escrevem com print, as mensagens do Maestro e os erros, com arquivo e linha (“Accents.lua:12: Duração inválida…”). Ele abre sozinho depois de um erro.

Na linha de baixo você pode digitar Lua para a partitura que está na frente e pressionar Enter: uma expressão mostra o seu valor (score.title, #score.staves), uma instrução é executada (score.title = "Sonata"), com um passo de Desfazer. As setas ↑ e ↓ trazem de volta as linhas anteriores.

Escrever um script

-- Um acento na primeira nota de cada compasso da flauta,
-- e as notas acima do C6 uma oitava abaixo
local flute = score.staves:find("Flute")
for _, measure in ipairs(flute.measures) do
  local first = measure:layer(1)[1]
  if first and first.isNote then first: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
  • Os números começam em 1, como em Lua e como os números de compasso: score.staves[1], measure:layer(1).
  • As propriedades são lidas e escritas com um ponto (e.duration = "8"), os métodos são chamados com dois-pontos (e:add("accent")).
  • As listas (score.staves, staff.measures, measure:layer(1), e.notes, measure.dynamics…) são contadas com # e percorridas com ipairs; os iteradores score:entries(), staff:entries(), measure:entries() dão todas as notas, acordes e pausas.
Objeto O que é Algumas propriedades e métodos
score a partitura title, composer, tempo, staves, measures, pageFormat; insertMeasures, deleteMeasures, setKey, setTime, transpose, addStaff
pauta score.staves[n] name, abbreviatedName, clef, measures; setClef, move, delete
compasso staff.measures[n] number, clef, dynamics, hairpins, texts, chords; layer(n), append, clear, fillWithRests
compasso da partitura score.measures[n] time, key; setTime, setKey
nota, acorde ou pausa measure:layer(1)[n] duration, isRest, isChord, tick, tied, articulations, notes; add, remove, transpose, setNotes, delete
altura e.notes[n] name, step, alter, octave, midi; transpose, delete
sinais measure.dynamics[n], score.slurs[n]… os campos do sinal (marking, tick…); delete; nas listas add({ … })

As durações se escrevem "w", "h", "q", "8", "16", "32" com pontos ("q."); as alturas "C4" (Dó central), "F#5", "Bb3"; os intervalos "P8", "-m3", "M2", "A4"; as articulações "accent", "staccato", "tenuto", "fermata"….

Além das propriedades da tabela, cada objeto alcança todos os campos da partitura pelo nome que eles têm no arquivo .maestro: e.hidden = true, e:set("pitches.1.notehead", "diamond"), staff:raw() (o objeto inteiro como tabela) e :replace(table), :fields(). Um nome errado dá um erro em vez de passar em silêncio. O próprio script dá a lista completa: maestro.schema() e maestro.schema("entry").

Percorrer a partitura

As funções eachentry, eachcell, eachstaff e each (as mesmas do RGP Lua) percorrem uma região: sem argumentos, a seleção ou, se não houver, a partitura inteira.

-- Um staccato em cada colcheia da seleção (ou da partitura)
for e in eachentry() do
  if e.isNote and e.duration == "8" then e:add("staccato") end
end
Função O que dá
eachentry(region, layer) notas, acordes e pausas, pauta por pauta e compasso por compasso; layer de 1 a 4 (todas, por padrão)
eachcell(region) cada compasso de cada pauta: for measure, staff in eachcell() do … end
eachstaff(region) as pautas
each(list), eachbackwards(list) os elementos de uma lista, ou as notas de um acorde (each(e)); de trás para frente você pode apagar enquanto avança
pairsbykeys(table) os pares de uma tabela na ordem das chaves

A região pode ser selection (só a seleção: nada, se não houver), score, uma pauta, um compasso (score.staves[1].measures[4] ou score.measures[4]) ou uma tabela como { staves = {1, 2}, measures = {5, 8} }: um número, ou o primeiro e o último; o que falta quer dizer todos. Existe também eachentrysaved, para quem vem do Finale, mas é igual a eachentry: no Maestro as alterações valem na hora.

Os comandos do editor

editor.perform faz o que você faria à mão com as paletas Articulações, Expressões, Linhas, Quiálteras, Ritornelos e Diagramação: ligaduras de expressão, reguladores, linhas de oitava, textos, casas de repetição, quebras de sistema… Ele age sobre uma região, como uma seleção: por padrão a seleção, ou uma entrada, uma lista de entradas, um compasso, uma pauta ou { staves = …, measures = … }. As perguntas do diálogo do comando são respondidas com opções.

editor.perform("slur", { staves = 1, measures = {1, 2} })            -- ligadura da primeira nota à última
editor.perform("crescendo", score.staves[1].measures[5])
editor.perform("ending", { measures = 8, numbers = {1} })            -- primeira casa
editor.perform("tuplet", e, { ratio = "5:4" })                      -- quintina a partir da entrada e
editor.perform("text", measure, { text = "dolce", kind = "expression" })
editor.perform("systemBreak", { measures = 9 })                      -- novo sistema a partir do compasso 9

editor.commands() lista os comandos com as suas opções. As articulações são adicionadas ou removidas, como na paleta. Se o editor recusa o comando (“Selecione pelo menos duas notas…”), o script para com um erro naquela linha, e esses comandos também ficam no único passo de Desfazer do script. Cada comando diagrama a partitura de novo: em laços longos, um comando sobre uma região grande é melhor que um por nota.

Nome, atalho e parâmetros

Um bloco de metadados opcional no início do script dá o nome no menu (em vários idiomas), uma descrição, um atalho e os parâmetros a perguntar antes de começar:

--[[ maestro
name = { en = "Transpose", it = "Trasponi" }
description = { en = "Transposes the selection by the chosen interval." }
shortcut = "mod+alt+shift+Y"   -- mod = ⌘ no Mac, Ctrl no Windows; ou "F9"
needsSelection = true
params = {
  { name = "interval", type = "text", default = "M2", label = { en = "Interval" } },
  { name = "times", type = "number", default = 1, min = 1, max = 4 },
}
]]
for e in selection.entries() do e:transpose(params.interval) end

O Maestro mostra um formulário com os parâmetros (na próxima vez ele propõe as respostas anteriores) e o script os encontra em params. Os tipos são number, text, boolean e choice (com choices = { … }). Um atalho já usado pelo Maestro não é atribuído. require("name") carrega módulos da pasta lib da pasta de scripts.

Numa pasta inteira

Scripts → Executar em uma Pasta… aplica um script a todas as partituras de uma pasta (também nas subpastas): Maestro, Finale, MusicXML, MuseScore, Guitar Pro e MIDI. No formulário você escolhe os parâmetros do script e o resultado: só o relatório, ou os arquivos salvos em outra pasta como documentos do Maestro, MusicXML, MusicXML comprimido, Finale (.musx) ou MIDI. Os arquivos originais nunca mudam. O console mostra o resultado arquivo por arquivo.

Pelo terminal: maestro-script

O comando maestro-script faz o mesmo sem janelas, para processamento em lote e para outros programas:

maestro-script run Accents.lua Scores/ --recursive --out Converted/ --format musicxml
maestro-script eval 'score.title' Scores/
maestro-script run Transpose.lua piece.musx -p interval=-M2 --out Output/ --json
maestro-script help

No Mac ele fica dentro do Maestro: Scripts → Instalar o comando maestro-script… o deixa disponível no Terminal. No Windows é o maestro-script.exe na pasta do Maestro (Scripts → O comando maestro-script… diz onde). Ele não está na versão da Mac App Store.

Com um assistente (Claude e outros)

maestro-script mcp é um servidor MCP: um assistente como o Claude pode abrir as suas partituras, escrever e executar scripts, olhar as páginas e salvar o resultado. Ele só lê e grava nas pastas que você lhe dá com --root; nunca sobrescreve o original nem, sem --allow-overwrite, um arquivo que já existe.

Com o Claude Code, no Mac:

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

Com o Claude Desktop, no arquivo de configuração dos servidores MCP:

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

No Windows o comando é %LOCALAPPDATA%\Programs\Maestro\maestro-script.exe. Depois é só pedir: “abra Minuet.musx, coloque um acento em cada primeiro tempo da flauta, mostre a primeira página e salve como Minuet-accents.maestro”.

Nas partituras abertas no Maestro

Um assistente também pode trabalhar nas partituras abertas no aplicativo enquanto você as olha. Isso vem desativado: ative com Scripts → Permitir que os Assistentes Modifiquem as Partituras Abertas e conecte o assistente com maestro-script mcp --app; com o Claude Code, no Mac:

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

O assistente vê as partituras abertas e a sua seleção; cada script dele é um passo de Desfazer chamado “Assistente” e aparece no console de scripts; quem salva os arquivos é você. A conexão fica no seu computador e na sua conta de usuário, sem rede.

Escrever scripts com um editor

A pasta de scripts também contém a descrição da interface para editores (.maestro/maestro.d.lua e .luarc.json): abra-a no Visual Studio Code com a extensão Lua para ter o preenchimento automático de propriedades e métodos e as verificações enquanto você digita.

Os dados originais do Finale

local ms = finale.others("measSpec", 1)[1]          -- o compasso 1 como o Finale o escreve
print(ms.beats, ms.divbeat, ms.width)
for e in score:entries() do
  local n = finale.entnum(e)                         -- a entrada do Finale de onde ela vem
  if n then
    local original = finale.entry(n)
    -- … por exemplo trazer algo que a importação não lê
  end
end
  • finale.others(tag, cmper, inci), finale.details(tag, cmper1, cmper2, inci), finale.texts(tag, number): os registros (os argumentos depois do nome são filtros opcionais); finale.entry(entnum), finale.header(), finale.options(tag), finale.tags() (os nomes presentes).
  • Um registro é uma tabela: _tag é o nome, atributos e elementos filhos são campos; um elemento vazio é true, um repetido é uma sequência (finale.list(x) sempre dá uma sequência).
  • finale.entryFor(entnum) e finale.entnum(entry), finale.staffFor(cmper) e finale.staffCmper(staff) ligam os objetos do Finale aos do Maestro.
  • É só leitura e não é salvo: depois de Salvar como .maestro e reabrir, finale deixa de existir. Para os arquivos .mus, contém os registros que o Maestro consegue ler.

Vindo do RGP Lua

A linguagem é a mesma, os objetos são os do Maestro:

RGP Lua (Finale) Maestro
plugindef() o bloco --[[ maestro … ]] no início do script
finenv.Region() selection; as funções de percurso sem região usam a seleção ou a partitura inteira
for e in eachentrysaved(finenv.Region()) for e in eachentry() (também eachentrysaved, eachcell, eachstaff, each, pairsbykeys)
finale.FCStaves, FCStaff score.staves, score.staves[n]
finale.FCMeasures, FCMeasure score.measures[n] (fórmula de compasso e tonalidade), staff.measures[n] (a música)
FCNoteEntryLayer, FCNoteEntry measure:layer(n), measure:layer(n)[i]
FCNote e.notes[i]
entry.Duration (EDU, semínima 1024) e.duration ("q") ou e.ticks (semínima 960)
FCExpression, FCSmartShape measure.dynamics, measure.texts, score.slurs, measure.hairpins…
entry:Save(), measure:Save() não é necessário: as alterações valem na hora (e se desfazem todas juntas)