10.5장

스크립트

스크립트는 손으로 하면 몇 시간 걸릴 일을 한 번에 해 줍니다. 모든 강박에 악센트 넣기, 반복되는 셈여림 지우기, 보표 조옮김, 악보 백 개의 악기 이름 바꾸기, 음역 검사 같은 일입니다. 스크립트는 Finale의 JW Lua와 RGP Lua 플러그인과 같은 언어인 Lua 5.4로 씁니다.

스크립트는 악보 전체(보표, 마디, 음표, 기호, 텍스트, 레이아웃)를 보고 바꿀 수 있습니다. 작업은 복사본에서 이루어집니다. 끝까지 실행되면 모든 변경이 실행 취소 한 단계가 되고, 오류로 멈추면 악보는 원래대로 남습니다. 스크립트는 닫힌 환경에서 실행되므로 파일을 읽거나 쓰거나, 네트워크를 쓰거나, 프로그램을 실행할 수 없습니다.

스크립트 메뉴

스크립트 폴더의 모든 .lua 파일은 스크립트 메뉴의 항목이 되고, 하위 폴더는 하위 메뉴가 됩니다. 처음에 Maestro가 Examples 폴더에 예제를 몇 개 복사해 둡니다. 텍스트 편집기로 열어 어떻게 동작하는지 살펴보세요. 그중 몇 개는 Finale용 RGP Lua 모음에서 가장 많이 쓰는 스크립트(화음에서 음 하나만 남기기, 옥타브 중복, 두 레이어 맞바꾸기, 쉼표의 아티큘레이션 제거, 음가 세기)를 다시 만든 것입니다.

  • 스크립트 → 스크립트 폴더 열기는 Finder나 파일 탐색기에서 폴더를 보여 줍니다(Mac에서는 ~/Library/Application Support/Maestro/Script, Windows에서는 %LOCALAPPDATA%\Maestro\Script). 새 파일이나 바꾼 파일은 다시 시작하지 않아도 바로 동작합니다.
  • 스크립트는 맨 앞의 악보에 적용됩니다. 스크립트가 선택 영역을 사용하면 선택 영역에, 그렇지 않으면 전체에 적용됩니다.
  • 다시 실행(⌥⌘U, Windows에서는 Ctrl+Alt+U)은 마지막 스크립트를 매개변수에 대한 같은 답으로 되풀이합니다.
  • 스크립트 중단은 너무 오래 걸리는 스크립트를 멈춥니다. 2분이 지나면 어쨌든 멈춥니다.
  • **스크립트 실행…**은 폴더 밖에 있는 것을 포함해 어떤 .lua 파일이든 엽니다.
  • 스크립트가 실행되는 동안에도 악보는 자유롭게 쓸 수 있습니다. 다만 그사이에 악보를 바꾸면 스크립트의 변경은 적용되지 않으므로(콘솔에 그렇게 표시됩니다), 직접 쓴 내용을 잃는 일은 없습니다.

iPad에서는 보면대의 스크립트 버튼이 Maestro의 Script 폴더(파일 앱: 나의 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
  • 번호는 Lua와 마디 번호처럼 1부터 시작합니다: 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와 같습니다)는 영역을 훑습니다. 인수가 없으면 선택 영역을, 선택 영역이 없으면 악보 전체를 훑습니다.

-- 선택 영역(또는 악보)의 모든 8분음표에 스타카토
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} })            -- 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()는 명령과 옵션의 목록을 보여 줍니다. 아티큘레이션은 팔레트에서처럼 추가되거나 제거됩니다. 편집기가 명령을 거부하면(“Select at least two notes…”) 스크립트는 그 줄에서 오류로 멈추며, 이 명령들도 스크립트의 실행 취소 한 단계 안에 들어갑니다. 명령마다 악보의 레이아웃을 다시 계산하므로, 긴 반복문에서는 음표마다 명령을 하나씩 쓰기보다 넓은 영역에 명령 하나를 쓰는 편이 좋습니다.

이름, 단축키, 매개변수

스크립트 맨 위에 선택 사항인 메타데이터 블록을 두면 메뉴에 표시할 이름(여러 언어로), 설명, 단축키, 시작하기 전에 물어볼 매개변수를 정할 수 있습니다.

--[[ 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, 4분음표 1024) e.duration("q") 또는 e.ticks(4분음표 960)
FCExpression, FCSmartShape measure.dynamics, measure.texts, score.slurs, measure.hairpins…
entry:Save(), measure:Save() 필요 없음: 변경이 즉시 적용됩니다(그리고 한꺼번에 되돌릴 수 있습니다)