第10.5章

脚本

脚本可以一次完成手工需要几个小时的工作:在每个强拍上加重音、移除重复的力度记号、为某行谱表移调、为一百份乐谱的乐器重新命名、检查音域。脚本用 Lua 5.4 编写,这也是 Finale 的 JW Lua 和 RGP Lua 插件所用的语言。

脚本可以看到整份乐谱(谱表、小节、音符、记号、文本、版面),并可以修改它。脚本在副本上工作:如果顺利完成,它的所有更改会合并为一步撤销;如果因错误而停止,乐谱保持原样。脚本在封闭的环境中运行:不能读写文件、使用网络或启动程序。

“脚本”菜单

脚本文件夹中的每个 .lua 文件都是“脚本”菜单中的一项;子文件夹则成为子菜单。第一次使用时,Maestro 会把一些示例复制到其中的示例文件夹:用文本编辑器打开它们,就能看到它们的写法。其中一些重写了 Finale 的 RGP Lua 脚本集中最常用的脚本(只保留和弦中的一个音、八度叠加、交换两个层、移除休止符上的演奏法记号、统计时值)。

  • “脚本”→“打开脚本文件夹”会在访达或文件资源管理器中显示该文件夹(在 Mac 上为 ~/Library/Application Support/Maestro/Script,在 Windows 上为 %LOCALAPPDATA%\Maestro\Script)。新建或修改的文件立即生效,无需重新启动。
  • 脚本作用于最前面的乐谱:如果脚本使用所选内容,则作用于所选内容,否则作用于全部内容。
  • 再次运行(⌥⌘U,在 Windows 上为 Ctrl+Alt+U)会用相同的参数答案重复运行上一个脚本。
  • 中断脚本会停止耗时过长的脚本;两分钟后脚本也会自动停止。
  • 运行脚本… 可以打开任意 .lua 文件,包括文件夹之外的文件。
  • 脚本运行时,乐谱仍可以操作;但如果在此期间修改了乐谱,脚本的更改就不会应用(控制台会说明),因此你写的内容永远不会丢失。

在 iPad 上,谱架上的脚本按钮会运行 Maestro 的 Script 文件夹中的脚本(在“文件”App 中:我的 iPad → Maestro → Script),例如在阅读时为乐曲移调;这些更改只用于阅读,文件不会改变。

脚本控制台

“脚本”→“脚本控制台”会显示脚本用 print 输出的内容、Maestro 的消息以及带文件名和行号的错误(“Accents.lua:12: Invalid duration…”)。出错后它会自动打开。

在底部的输入行中,可以为最前面的乐谱输入 Lua 并按 Enter:表达式会显示其值(score.title、#score.staves),语句会被执行(score.title = "Sonata"),并作为一步撤销。↑ 和 ↓ 箭头可以调出之前输入的行。

编写脚本

-- 在长笛每个小节的第一个音符上加重音,
-- 并把高于 C6 的音符降低一个八度
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
  • 编号从 1 开始,与 Lua 和小节号一样:score.staves[1]、measure:layer(1)。
  • 属性用点号读写(e.duration = "8"),方法用冒号调用(e:add("accent"))。
  • 列表(score.staves、staff.measures、measure:layer(1)、e.notes、measure.dynamics…)用 # 计数,用 ipairs 遍历;迭代器 score:entries()、staff:entries()、measure:entries() 会给出所有音符、和弦和休止符。
对象 含义 部分属性和方法
score 乐谱 title、composer、tempo、staves、measures、pageFormat;insertMeasures、deleteMeasures、setKey、setTime、transpose、addStaff
谱表 score.staves[n] name、abbreviatedName、clef、measures;setClef、move、delete
小节 staff.measures[n] number、clef、dynamics、hairpins、texts、chords;layer(n)、append、clear、fillWithRests
乐谱小节 score.measures[n] time、key;setTime、setKey
音符、和弦或休止符 measure:layer(1)[n] duration、isRest、isChord、tick、tied、articulations、notes;add、remove、transpose、setNotes、delete
音高 e.notes[n] name、step、alter、octave、midi;transpose、delete
记号 measure.dynamics[n]、score.slurs[n]… 记号的字段(marking、tick…);delete;在列表中用 add({ … })

时值写作 "w"、"h"、"q"、"8"、"16"、"32",附点写作 "q.";音高写作 "C4"(中央 C)、"F#5"、"Bb3";音程写作 "P8"、"-m3"、"M2"、"A4";演奏法记号写作 "accent"、"staccato"、"tenuto"、"fermata"…。

除了表中的属性,每个对象都可以按 .maestro 文件中的名称访问乐谱的所有字段:e.hidden = true、e:set("pitches.1.notehead", "diamond")、staff:raw()(以表的形式给出整个对象)以及 :replace(table)、:fields()。名称写错会报错,而不会悄悄忽略。完整的列表可以由脚本本身给出:maestro.schema() 和 maestro.schema("entry")。

遍历乐谱

函数 eachentry、eachcell、eachstaff 和 each(与 RGP Lua 中相同)会遍历一个区域:不带参数时,遍历所选内容;如果没有所选内容,则遍历整份乐谱。

-- 在所选内容(或整份乐谱)的每个八分音符上加断奏
for e in eachentry() do
  if e.isNote and e.duration == "8" then e:add("staccato") end
end
函数 给出的内容
eachentry(region, layer) 音符、和弦和休止符,逐谱表、逐小节;layer 为 1 到 4(默认为全部)
eachcell(region) 每行谱表的每个小节:for measure, staff in eachcell() do … end
eachstaff(region) 各行谱表
each(list)、eachbackwards(list) 列表中的元素,或和弦中的音符(each(e));倒序遍历时可以边遍历边删除
pairsbykeys(table) 按键的顺序给出表中的键值对

区域可以是 selection(只有所选内容:没有所选内容时为空)、score、一行谱表、一个小节(score.staves[1].measures[4] 或 score.measures[4]),或者 { staves = {1, 2}, measures = {5, 8} } 这样的表:一个数字,或者第一个和最后一个;缺少的部分表示全部。为了方便从 Finale 转来的用户,还有 eachentrysaved,但它与 eachentry 相同:在 Maestro 中,更改会立即生效。

编辑器的命令

editor.perform 可以完成你用演奏法、表情记号、线条、连音、反复记号与跳转以及版面面板手工完成的操作:圆滑线、渐强渐弱记号、八度记号、文本、房子记号、谱行换行…它像所选内容一样作用于一个区域:默认为所选内容,也可以是一个条目、条目列表、一个小节、一行谱表或 { staves = …, measures = … }。命令对话框中的问题用选项来回答。

editor.perform("slur", { staves = 1, measures = {1, 2} })            -- 从第一个音符到最后一个音符的圆滑线
editor.perform("crescendo", score.staves[1].measures[5])
editor.perform("ending", { measures = 8, numbers = {1} })            -- 第一房
editor.perform("tuplet", e, { ratio = "5:4" })                      -- 从条目 e 开始的五连音
editor.perform("text", measure, { text = "dolce", kind = "expression" })
editor.perform("systemBreak", { measures = 9 })                      -- 从第 9 小节开始新谱行

editor.commands() 会列出这些命令及其选项。和在面板中一样,演奏法记号会被添加或移除。如果编辑器拒绝执行命令(“请至少选择两个音符…”),脚本会在该行报错停止,这些命令也包含在脚本唯一的一步撤销中。每条命令都会重新排版乐谱:在较长的循环中,对一个大区域执行一条命令,比对每个音符执行一条命令要好。

名称、快捷键和参数

脚本顶部可以有一个可选的元数据块,用来指定菜单中的名称(可以有多种语言)、说明、快捷键以及开始前要询问的参数:

--[[ maestro
name = { en = "Transpose", it = "Trasponi" }
description = { en = "Transposes the selection by the chosen interval." }
shortcut = "mod+alt+shift+Y"   -- mod 在 Mac 上为 ⌘,在 Windows 上为 Ctrl;也可以是 "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

Maestro 会显示一个包含这些参数的表单(下次会建议上一次的答案),脚本在 params 中获取它们。类型有 number、text、boolean 和 choice(配合 choices = { … })。已被 Maestro 使用的快捷键不会被分配。require("name") 会从脚本文件夹的 lib 文件夹中加载模块。

对整个文件夹运行

“脚本”→“在文件夹上运行…”会把脚本应用到某个文件夹(包括子文件夹)中的所有乐谱:Maestro、Finale、MusicXML、MuseScore、Guitar Pro 和 MIDI。在表单中选择脚本的参数和结果:只要报告,或者把文件以 Maestro 文稿、MusicXML、压缩的 MusicXML、Finale(.musx)或 MIDI 格式保存到另一个文件夹中。原始文件永远不会改变。 控制台会逐个文件显示结果。

从终端运行:maestro-script

maestro-script 命令无需窗口就能完成同样的工作,适用于批量处理和其他程序:

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

在 Mac 上,它位于 Maestro 内部:“脚本”→“安装 maestro-script 命令…”可以让它在“终端”中使用。在 Windows 上,它是 Maestro 文件夹中的 maestro-script.exe(“脚本”→“maestro-script 命令…”会说明它的位置)。Mac App Store 版本中没有该命令。

通过助手使用(Claude 等)

maestro-script mcp 是一个 MCP 服务器:Claude 等助手可以打开你的乐谱、编写和运行脚本、查看页面并保存结果。它只在你用 --root 指定的文件夹中读写;它永远不会覆盖原始文件,没有 --allow-overwrite 时也不会覆盖已存在的文件。

在 Mac 上使用 Claude Code:

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

使用 Claude Desktop 时,在 MCP 服务器的配置文件中写入:

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

在 Windows 上,命令为 %LOCALAPPDATA%\Programs\Maestro\maestro-script.exe。然后只需提出请求:“打开 Minuet.musx,在长笛的每个强拍上加重音,给我看第一页,然后另存为 Minuet-accents.maestro”。

处理 Maestro 中打开的乐谱

助手也可以在你查看时处理应用中打开的乐谱。该功能默认关闭:用“脚本”→“允许助手编辑打开的乐谱”打开它,并用 maestro-script mcp --app 连接助手;在 Mac 上使用 Claude Code:

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

助手可以看到打开的乐谱和你的所选内容;它的每个脚本都是名为“助手”的一步撤销,并显示在脚本控制台中;文件由你来保存。连接只在你的电脑和你的用户帐户内进行,不经过网络。

用编辑器编写脚本

脚本文件夹中还有供编辑器使用的接口说明(.maestro/maestro.d.lua 和 .luarc.json):用装有 Lua 扩展的 Visual Studio Code 打开该文件夹,就能在输入时获得属性和方法的自动补全以及检查。

Finale 的原始数据

local ms = finale.others("measSpec", 1)[1]          -- Finale 中写法的第 1 小节
print(ms.beats, ms.divbeat, ms.width)
for e in score:entries() do
  local n = finale.entnum(e)                         -- 它所来自的 Finale 条目
  if n then
    local original = finale.entry(n)
    -- … 例如带入导入时没有读取的内容
  end
end
  • finale.others(tag, cmper, inci)、finale.details(tag, cmper1, cmper2, inci)、finale.texts(tag, number):记录(名称之后的参数是可选的筛选条件);finale.entry(entnum)、finale.header()、finale.options(tag)、finale.tags()(存在的名称)。
  • 记录是一个表:_tag 是名称,属性和子元素是字段;空元素为 true,重复的元素为序列(finale.list(x) 始终给出序列)。
  • finale.entryFor(entnum) 和 finale.entnum(entry)、finale.staffFor(cmper) 和 finale.staffCmper(staff) 用于在 Finale 的对象和 Maestro 的对象之间建立联系。
  • 这些数据是只读的,也不会保存:以 .maestro 格式保存并重新打开后,finale 就不存在了。对于 .mus 文件,它包含 Maestro 能够读取的记录。

从 RGP Lua 转来

语言相同,对象是 Maestro 的:

RGP Lua(Finale) Maestro
plugindef() 脚本顶部的 --[[ maestro … ]] 块
finenv.Region() selection;不带区域的遍历函数使用所选内容或整份乐谱
for e in eachentrysaved(finenv.Region()) for e in eachentry()(还有 eachentrysaved、eachcell、eachstaff、each、pairsbykeys)
finale.FCStaves、FCStaff score.staves、score.staves[n]
finale.FCMeasures、FCMeasure score.measures[n](拍号和调号)、staff.measures[n](音乐内容)
FCNoteEntryLayer、FCNoteEntry measure:layer(n)、measure:layer(n)[i]
FCNote e.notes[i]
entry.Duration(EDU,四分音符 1024) e.duration("q")或 e.ticks(四分音符 960)
FCExpression、FCSmartShape measure.dynamics、measure.texts、score.slurs、measure.hairpins…
entry:Save()、measure:Save() 不需要:更改会立即生效(并且一起撤销)