Skip to content

開発ノウハウ

本章では、現時点で主流の MuseScore 4.x 向けに、レガシー QML プラグインを効率よく作るための実務的なノウハウをまとめます。

  1. VSCode による開発環境の構築例
  2. UI の実装例
  3. ソース分割(UI・橋渡し・動作)
  4. 独自フォントの利用 — 詳細は 独自フォント開発
  5. 国際化(i18n) — 詳細は 国際化開発
  6. import のバージョン指定
  7. 実務知見・落とし穴
  8. 参考リンク

数字譜・譜線非表示・改行・小節幅揃え・音符のテキスト置換などは、別ページ 譜表・記譜操作ガイド にまとめています。

対象: import MuseScore 3.0 とルート MuseScore { } によるレガシー QML プラグイン(4.x で利用中の形式)。
本リポジトリのソースは 5.0(prerelease)であり、Extensions(manifest.json + JS)も含まれますが、本章の手順は 4.x ユーザー向けです。5.x Extensions については 概要 2 章 を参照してください。

前章: API リファレンス / 概要: プラグイン開発の概要


1. VSCode による開発環境の構築例(レガシー QML / MuseScore 4.x)

4.x のプラグインは、主に次のテキスト/リソースで構成されます。

ファイル役割
*.qml本体。ルートは必ず MuseScore { }。同階層に UI 部品用の子 .qml を置いてもよい
(任意).js楽譜処理・計算など動作ロジック(§3
(任意)画像thumbnailName で指定するサムネイルなど
(任意)翻訳translations/ 配下の翻訳ファイル

多くのプラグインは 単一の .qml か、サブフォルダにまとめた一式です。
依存の .js / 子 .qml がある場合は 専用サブフォルダにまとめる運用を推奨します(§3)。
専用 IDE は不要で、VSCode(または互換の Cursor など)で編集し、実行は MuseScore 4.x 側で行う構成が実務的です。

1.1 前提ソフトウェアのインストール

次を用意します。

ソフトウェア用途入手先の例
MuseScore Studio 4.xプラグインの実行・確認MuseScore 公式
Visual Studio Codeソース編集https://code.visualstudio.com/
(任意)Git版管理https://git-scm.com/

インストール手順の概要(Windows):

  1. MuseScore Studio 4.x をインストールし、起動できることを確認する
  2. VSCode をインストールする(ユーザーインストーラ/システムインストーラどちらでも可)
  3. 作業用フォルダを決める(本ワークスペース、またはユーザー Plugins フォルダ直下など)
  4. VSCode でフォルダを開く(File → Open Folder…

macOS / Linux でも同様です。Plugins フォルダのパスだけ OS ごとに異なります(次節参照)。

1.2 VSCode 拡張機能(プラグイン)の追加

VSCode の Extensions ビュー(Ctrl+Shift+X)から、次のような拡張を入れると編集体験が良くなります。
括弧内は Marketplace の拡張 ID(インストール時の識別子)です。

拡張名拡張 ID目的・備考
QML(bbenoist)bbenoist.QML*.qml のシンタックスハイライト。Qt 本体のインストール不要で、MuseScore プラグイン編集には手軽
Qt Qml(Qt Group)TheQtCompany.qt-qml公式の QML サポート(ハイライト・補完・Lint など)。Qt の登録や QML Language Server の設定が必要になる場合あり
Error Lens(任意)usernamehw.errorlens行内に診断を表示し、見やすくする
GitLens(任意)eamodio.gitlensGit の履歴・注釈をエディタ上で確認する

コマンドラインからのインストール例:

powershell
code --install-extension bbenoist.QML
# または公式 Qt 拡張を使う場合:
# code --install-extension TheQtCompany.qt-qml

必須ではありません。最低限、bbenoist.QML によるハイライトだけでも開発は可能です。
TheQtCompany.qt-qml は機能が豊富ですが、MuseScore 固有の MuseScore { } / curScore などは補完対象外です(一般的な Qt Quick 向け)。

推奨ワークスペース設定の例(.vscode/settings.json):

json
{
    "files.associations": {
        "*.qml": "qml"
    },
    "editor.insertSpaces": true,
    "editor.tabSize": 4,
    "files.eol": "\n"
}

1.3 開発/デバッグの流れ

典型的なサイクルは次のとおりです。

mermaid
flowchart LR
    A[VSCode で QML 編集] --> B[Plugins フォルダへ配置]
    B --> C[MuseScore で有効化]
    C --> D[Plugins メニューから実行]
    D --> E[ログ / 楽譜で確認]
    E --> A

手順

  1. Plugins フォルダを用意する
    ユーザー Plugins フォルダに、プラグイン専用のサブフォルダを作成します(依存ファイルがある場合は必須に近い運用です)。

    OS既定パス
    WindowsC:\Users\<ユーザー名>\Documents\MuseScore4\Plugins\<プラグイン名>\
    macOS~/Documents/MuseScore4/Plugins/<プラグイン名>/
    Linux~/Documents/MuseScore4/Plugins/<プラグイン名>/

    実際のパスは Preferences → Folders → Plugins で確認・変更できます。
    公式 Handbook: Plugins

  2. VSCode で .qml を書く
    ルートは MuseScore { }、エントリは onRun です。4.x 向けには title / thumbnailName / categoryCode なども付与します。

    Hello World の例:

    qml
    import QtQuick
    import MuseScore 3.0
    
    MuseScore {
        version: "1.0"
        description: "Hello World plugin"
        title: "Hello World"
        categoryCode: "composing-arranging-tools"
        // thumbnailName: "hello.png"  // 任意。サブフォルダ内の画像
    
        onRun: {
            console.log("Hello World!");
            quit();
        }
    }
    • import MuseScore 3.0 … API 利用に必要(URI 名は歴史的に 3.0)
    • quit() … プラグイン終了(現行実装では quit() を推奨。旧例の Qt.quit() も見かける)
    • 同梱サンプル: share/plugins/note_namestuning など)
  3. MuseScore で有効化する
    MuseScore 4.x を起動し、Home → Plugins(または Plugins → Manage plugins…)で対象を Enable します。
    初回以降、.qml を上書きした場合は 再実行、反映されないときは MuseScore の再起動 を行います。

  4. スコアを開いて実行する
    楽譜を開いた状態で Plugins メニュー(またはカテゴリ付きサブメニュー)から実行します。
    requiresScore が既定のままなら、スコアが必要です。

  5. 結果を確認する(デバッグ)

    注(MuseScore 4.x): 3.x にあった Plugins → Plugin Creator(編集・実行・コンソール一体)や、プラグイン開発用の 組み込みデバッグコンソールは搭載されていません
    View → Console も現行 4.x の一般配布ビルドでは見当たらないことが多く、console.log(...) の行き先も OS・ビルド種別によって見え方が変わります。以下は コンソール代替 を含む実務的な確認手段です。

    手段用途備考
    楽譜上の見た目・undo音符追加・色変更などの成否いちばん確実。副作用のある処理の第一確認
    アプリログ読込失敗・QML モジュールエラーWindows: %LOCALAPPDATA%\MuseScore\MuseScore4\logs\エラーは出やすいが、console.log 本文は出ないことが多い
    端末から --debug / -d で起動標準出力へのログ確認Linux では有効な報告が多い。Windows の一般配布ビルドでは端末に console.log が出ないことが多い
    FileIO でログファイルへ書く変数・分岐の追跡4.x でいちばん再現性が高いプリント代替。後述
    dialog 内の Text / TextAreaUI 上にメッセージ表示pluginType: "dialog" 向け。別ウィンドウで状態を見られる
    musescore-plugin-liblog.jsバッファ+ダイアログ/FileIO4.4+ 向け共有ライブラリ。ユーザー向けは dump、開発向けは writeFile
    コミュニティ製コンソール/ログ部品GUI やファイルへの集約Plugin Development ConsoleDebugTools など(非公式)

    console.log 自体は書いておいて問題ありません(デバッグビルドや一部環境では端末に出ます)が、リリース相当の 4.x では「見えない前提」で代替を用意するのが安全です。

    qml
    onRun: {
        console.log("curScore:", curScore);
        if (!curScore) {
            console.log("error: no score");
            quit();
            return;
        }
        curScore.startCmd();
        // … 処理 …
        curScore.endCmd();
        quit();
    }

    代替例 A: FileIO で一時フォルダに追記ログAPI リファレンス §9

    qml
    import QtQuick
    import MuseScore 3.0
    import FileIO 3.0
    
    MuseScore {
        title: "Debug sample"
        categoryCode: "composing-arranging-tools"
    
        FileIO {
            id: debugLog
            // FileIO 内のバインディングでは tempPath() 等をそのまま呼べる
            source: tempPath() + "/musescore-debug-sample.log"
        }
    
        function dlog(msg) {
            var line = new Date().toISOString() + " " + msg + "\n"
            var prev = debugLog.exists() ? debugLog.read() : ""
            debugLog.write(prev + line)
            console.log(msg)  // 見える環境向けに併用
        }
    
        onRun: {
            dlog("curScore: " + curScore)
            if (!curScore) {
                dlog("error: no score")
                quit()
                return
            }
            // … 処理 …
            quit()
        }
    }

    注: FileIO.write はユーザーデータ/一時フォルダなど書き込み可能なパスに限られます。
    新しい 4.x では pluginsUserPath() / pluginDirectoryPath() も使えます(プラグインフォルダ直下にログを置きたい場合)。絶対パスのハードコードは避けてください。

    代替例 B: dialog にデバッグ行を出す

    qml
    // pluginType: "dialog" の UI 内
    TextArea {
        id: debugPane
        readOnly: true
        wrapMode: TextEdit.Wrap
    }
    // 処理中: debugPane.text += "pitch=" + note.pitch + "\n"
  6. 修正を繰り返す
    VSCode で保存 → MuseScore で再実行(必要なら再起動)→ 確認、を繰り返します。

作業ディレクトリの取り方(例)

方式内容向き
A. Plugins フォルダを直接編集VSCode で ...\MuseScore4\Plugins\<名前> を開く反復開発が速い
B. リポジトリで編集し、配置先へコピー/同期版管理用フォルダで編集し、実行時だけ Plugins へ反映本格開発・共有

Windows で B を使う場合の例(PowerShell):

powershell
Copy-Item -Recurse -Force .\myplugin\* `
  "$env:USERPROFILE\Documents\MuseScore4\Plugins\myplugin\"

1.4 限界・注意点

VSCode は編集環境として適していますが、次の点はあらかじめ理解してください。

項目内容
実行環境は MuseScoreVSCode の Run / Debug ではプラグインは動きません。実行・確認は必ず MuseScore 4.x 上で行います。
組み込みデバッグコンソールは無い4.x には 3.x の Plugin Creator コンソール相当がありません。console.log も一般配布ビルドでは見えないことが多いです。FileIO ログ・dialog 表示・アプリログを併用してください(§1.3 手順 5)。
ブレークポイント付きデバッガは基本不可VSCode から MuseScore の QML ランタイムにアタッチする公式手段はありません。プリント/ファイル/UI 表示による確認が中心です。
MuseScore API の補完が弱いcurScore / Cursor / Element など専用 API は、標準では IntelliSense が出にくいです。本ドキュメント、API リファレンスsrc/engraving/api/v1/、同梱サンプルを参照してください。
QML プレビューの限界Qt Creator のデザイナは一般的な Qt Quick 向けです。ルート MuseScore { } や楽譜 API のライブプレビューは期待できません。
反映タイミングファイル保存だけでは更新されないことがあります。再実行や MuseScore 再起動が必要な場合があります。
Plugin Creator は前提にしないMuseScore 3.x にあった Plugins → Plugin CreatorCtrl+Shift+P、編集+コンソール一体)は 4.x では利用できません(後継の組み込みコンソールも未搭載)。編集は外部エディタ、実行はアプリ側、ログは代替手段、という分担になります。
4.4 以降は Qt 6 互換が必要MuseScore Studio 4.4+ では、Qt 6 向けに書き換えていない QML が Plugins 一覧に出ないことがあります。import QtQuick(バージョン番号なし)など、4.4 向けの書き方に合わせてください。詳細は Plugins for 4.x および Updating plugins for 4.4 を参照。
サブフォルダ配置を推奨サムネイルや翻訳を使う場合、プラグイン一式を専用サブフォルダに置く運用が安全です。

Qt Creator との使い分け

ツール向いていること
VSCode / CursorQML の編集、Git、ドキュメント参照、日常開発
Qt Creator(任意)一般的な QML 構文の確認。MuseScore プラグインの必須ツールではない

2. UI の実装例(pluginType: "dialog"

即実行型(pluginType 未指定)は onRun だけで処理して quit() します。
設定画面や確認が必要な場合は pluginType: "dialog" にし、ルート MuseScore の子として UI を置きます。

同梱の実例:

プラグインパスUI の特徴
Mirror Intervalsintervals/mirror-intervals-3.qmlドロップダウン+Apply/Cancel、MessageDialog
Tuningtuning/tuning.qmlMuse.UiComponents 中心の本格ダイアログ
Lilyricslilyrics/lilyrics.qmlTextAreaSpinBoxComboBoxCheckBox

2.1 ダイアログの骨格

qml
import QtQuick
import QtQuick.Layouts
import QtQuick.Dialogs

import MuseScore 3.0
import Muse.Ui
import Muse.UiComponents

MuseScore {
    version: "1.0"
    title: "My Dialog Plugin"
    description: "UI 付きプラグインの例"
    pluginType: "dialog"
    categoryCode: "composing-arranging-tools"
    requiresScore: true

    width: 360
    height: 200

    onRun: {
        // ダイアログ表示時に一度呼ばれる。
        // 致命的な前提不足ならここで MessageDialog を出して quit() してもよい。
        if (!curScore) {
            errorDialog.text = qsTr("スコアが開かれていません。")
            errorDialog.open()
        }
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 12
        spacing: 8

        StyledTextLabel {
            text: qsTr("オプションを選んで Apply を押してください。")
            Layout.fillWidth: true
            wrapMode: Text.WordWrap
        }

        // … コントロール類 …

        RowLayout {
            Layout.alignment: Qt.AlignRight
            spacing: 8

            FlatButton {
                text: qsTranslate("PrefsDialogBase", "Cancel")
                onClicked: quit()
            }
            FlatButton {
                text: qsTranslate("PrefsDialogBase", "Apply")
                accentButton: true
                onClicked: {
                    // curScore.startCmd() / endCmd() で変更を囲む
                    quit()
                }
            }
        }
    }

    MessageDialog {
        id: errorDialog
        title: qsTr("Error")
        text: ""
        onAccepted: quit()
    }
}

ポイント:

  • width / height … ダイアログの初期サイズ
  • quit() … ウィンドウを閉じてプラグインを終了(Cancel / Apply 後)
  • Apply/Cancel の文言は同梱プラグイン同様 qsTranslate("PrefsDialogBase", …) がよく使われる

2.2 よく使うコントロール

見た目を MuseScore に合わせるなら Muse.UiComponents(およびテーマ用の Muse.Ui)を優先します。
QtQuick.Controls の標準コントロールも動作します(lilyrics など)。

コントロール主な import用途
StyledTextLabelMuse.UiComponentsラベル・説明文
FlatButtonMuse.UiComponentsボタン(accentButton: true で強調)
CheckBoxMuse.UiComponents または QtQuick.Controlsオン/オフ
StyledDropdownMuse.UiComponents一覧からの選択(推奨)
ComboBoxQtQuick.Controls一覧からの選択(標準)
SpinBoxQtQuick.Controls整数の増減
IncrementalPropertyControlMuse.UiComponents小数付き数値(tuning で使用)
TextField / TextAreaQtQuick.Controls1 行/複数行テキスト
RoundedRadioButton / FlatRadioButtonMuse.UiComponents択一

ラベルとボタン

qml
StyledTextLabel {
    text: qsTr("処理対象")
}

FlatButton {
    text: qsTr("実行")
    toolTipTitle: qsTr("選択範囲に処理を適用します")
    onClicked: { /* … */ }
}

FlatButton {
    text: qsTranslate("PrefsDialogBase", "Apply")
    accentButton: true   // 主ボタン
    onClicked: { /* … */ }
}

チェックボックス

Muse.UiComponentsCheckBox は、クリック時に自分で checked をトグルする書き方が同梱例で多いです。

qml
CheckBox {
    id: skipTies
    text: qsTr("タイをスキップ")
    checked: true
    onClicked: checked = !checked
}

ドロップダウン(選択)

StyledDropdownmirror-intervals-3.qml と同型):

qml
StyledDropdown {
    id: pivotNote
    model: [
        { "text": "C",  "note": 0 },
        { "text": "D",  "note": 2 },
        { "text": "E",  "note": 4 }
    ]
    currentIndex: 0
    onActivated: function(index, value) {
        currentIndex = index
    }
}

// 選択値の取り出し例
// pivotNote.model[pivotNote.currentIndex].note

ComboBox(文字列リスト):

qml
ComboBox {
    id: positionBox
    model: [qsTr("Above"), qsTr("Below")]
    currentIndex: 1
    // 選択文字列: positionBox.currentText
    // インデックス: positionBox.currentIndex
}

スピンボックス/数値

qml
RowLayout {
    StyledTextLabel {
        text: qsTr("声部:")
        Layout.alignment: Qt.AlignVCenter
    }
    SpinBox {
        id: voiceBox
        from: 1
        to: 4
        value: 1
    }
}

小数(cents など)は IncrementalPropertyControl が便利です(tuning.qml):

qml
IncrementalPropertyControl {
    currentValue: 0.0
    decimals: 1
    step: 0.1
    minValue: -99.9
    maxValue: 99.9
    onValueEdited: function(newValue) {
        console.log("new value:", newValue)
    }
}

テキスト入力

qml
TextField {
    id: nameField
    placeholderText: qsTr("名前")
    Layout.fillWidth: true
}

TextArea {
    id: lyricsArea
    Layout.fillWidth: true
    Layout.fillHeight: true
    wrapMode: TextEdit.Wrap
    selectByMouse: true
    textFormat: TextEdit.PlainText
}

テーマ色を使う例(lilyrics.qml):

qml
Rectangle {
    color: ui.theme.textFieldColor
    border.color: ui.theme.strokeColor
    border.width: Math.max(ui.theme.borderWidth, 1)
    radius: 3
}

uiimport Muse.Ui で利用できます。

2.3 メッセージダイアログ

エラーや確認には MessageDialogQtQuick.Dialogs)を使います。

qml
import QtQuick.Dialogs

// …

function showError(message) {
    errorDialog.text = message
    errorDialog.open()
}

MessageDialog {
    id: errorDialog
    title: qsTr("Error")
    text: ""
    onAccepted: {
        // 続行不可なら quit()、ダイアログだけ閉じるなら何もしない
        quit()
    }
}

確認(Yes / No)の例:

qml
MessageDialog {
    id: confirmDialog
    title: qsTr("確認")
    text: qsTr("選択範囲を変更します。よろしいですか?")
    buttons: MessageDialog.Yes | MessageDialog.No

    onButtonClicked: function(button, role) {
        if (button === MessageDialog.Yes) {
            // 処理を実行
        }
        // No の場合は何もしない/quit()
    }
}

補足: 同梱プラグインでは import QtQuick.Dialogs が省略されていることがありますが、新規作成時は明示 import を推奨します。Qt 6 / MuseScore 4.4+ 向けです。

2.4 ファイル選択ダイアログ

ファイルの読み書き UI には FileDialog を使います。パスが決まれば、プラグイン API の FileIOAPI リファレンス §9)で内容を読めます。

qml
import QtQuick.Dialogs

FlatButton {
    text: qsTr("ファイルを開く…")
    onClicked: openDialog.open()
}

FileDialog {
    id: openDialog
    title: qsTr("ファイルを選択")
    fileMode: FileDialog.OpenFile
    nameFilters: [qsTr("テキスト (*.txt)"), qsTr("すべて (*.*)")]
    onAccepted: {
        // selectedFile は url(例: file:///C:/...)
        console.log("selected:", selectedFile)
        // FileIO 等で読み込み
    }
}

FileDialog {
    id: saveDialog
    title: qsTr("名前を付けて保存")
    fileMode: FileDialog.SaveFile
    nameFilters: [qsTr("テキスト (*.txt)")]
    onAccepted: {
        console.log("save to:", selectedFile)
    }
}

フォルダ選択:

qml
FolderDialog {
    id: folderDialog
    title: qsTr("フォルダを選択")
    onAccepted: console.log("folder:", selectedFolder)
}

2.5 レイアウトの定石

要素用途
ColumnLayout / RowLayout / GridLayout整列(import QtQuick.Layouts
anchors.fill: parent + anchors.marginsダイアログいっぱいに配置
Layout.fillWidth / Layout.fillHeight余白の吸収
Item { Layout.fillWidth: true }ボタン行で右寄せするためのスペーサ

Apply / Cancel を右下に置く例:

qml
RowLayout {
    Layout.fillWidth: true
    Item { Layout.fillWidth: true }
    FlatButton {
        text: qsTranslate("PrefsDialogBase", "Cancel")
        onClicked: quit()
    }
    FlatButton {
        text: qsTranslate("PrefsDialogBase", "Apply")
        accentButton: true
        onClicked: {
            applySomething()
            quit()
        }
    }
}

2.6 UI 実装時の注意

項目内容
変更は startCmd / endCmdApply 時の楽譜変更は必ずコマンドで囲む
ダイアログ中も curScore を参照可能ただしユーザーがスコアを閉じた場合などに注意
見た目の統一可能なら FlatButton / StyledDropdown / StyledTextLabel を使う
即実行と dialog の混在UI なし処理だけなら pluginType を付けない方が単純
Qt 6import QtQuick 2.x 形式より、バージョンなしの import QtQuick を使う(4.4+)
終了は quit()Qt.quit() は MuseScore 本体ごと終了させることがある(§7.4
スコア無しでの終了全スコア閉鎖時に dialog を閉じるとアプリ終了することがある。requiresScore: false を検討(§7.5

3. ソース分割(UI・橋渡し・動作)

dialog や Apply 処理が肥えてきたら、見た目(UI)・橋渡し(ボタン等からの呼び出し)・楽譜処理(動作) をファイル単位で分けます。
Qt の標準的な仕組み(同階層の .qml 型、import "….js" as …)で実現でき、コミュニティでも実績があります。

Hello World 級や処理が数行のプラグインまで強制分割する必要はありません。次のようなときに導入します。

目安
Apply 内の Cursor 走査・分岐が長いUI と楽譜ロジックが同じファイルで読みにくい
同じ計算を複数ボタンから呼ぶロジックを .js にまとめて再利用したい
オプション行などの UI 塊が繰り返される同階層の部品 .qml に切り出したい

3.1 責務の分け方

mermaid
flowchart TB
  subgraph folder [myplugin/]
    Main["myplugin.qml\nMuseScore root + UI + 橋渡し"]
    Panel["OptionalPanel.qml\n再利用 UI 部品"]
    Logic["logic.js\n楽譜処理・計算"]
  end
  Main -->|onClicked 等| Logic
  Main -->|子として配置| Panel
  Logic -->|結果・エラー文字列| Main
置き場所責務
UIエントリ .qml と任意の同階層 .qmlレイアウト・コントロール・MessageDialog。楽譜ループは書かない
橋渡しエントリ .qml に最小限だけ残すonClicked / onAccepted で「入力を集め → ロジック呼び出し → 結果表示/quit()」だけ行う
動作(ロジック)logic.js(必要なら複数)Cursor 走査、startCmd/endCmd、音高計算など。UI の id を直接触らない

3.2 推奨ディレクトリ

myplugin/
  myplugin.qml      # エントリ(ルートは必ず MuseScore { })
  logic.js          # 動作
  OptionRow.qml     # 任意。同階層なら import なしで型名として使える
  translations/     # 既存方針どおり([§5](#5-国際化i18n) / [国際化開発](./i18n.md))

3.3 エントリ QML(UI + 橋渡し)

qml
import QtQuick
import QtQuick.Layouts
import QtQuick.Dialogs

import MuseScore 3.0
import Muse.UiComponents
import "logic.js" as Logic

MuseScore {
    version: "1.0"
    title: "Split Sample"
    description: "UI と動作を分けた例"
    pluginType: "dialog"
    categoryCode: "composing-arranging-tools"
    requiresScore: true

    width: 360
    height: 160

    function optionsFromUi() {
        return { skipTies: skipTies.checked }
    }

    function showError(message) {
        errorDialog.text = message
        errorDialog.open()
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 12
        spacing: 8

        CheckBox {
            id: skipTies
            text: qsTr("タイをスキップ")
            checked: true
            onClicked: checked = !checked
        }

        RowLayout {
            Layout.alignment: Qt.AlignRight
            spacing: 8

            FlatButton {
                text: qsTranslate("PrefsDialogBase", "Cancel")
                onClicked: quit()
            }
            FlatButton {
                text: qsTranslate("PrefsDialogBase", "Apply")
                accentButton: true
                onClicked: {
                    var err = Logic.apply(curScore, optionsFromUi())
                    if (err) {
                        showError(err)
                        return
                    }
                    quit()
                }
            }
        }
    }

    MessageDialog {
        id: errorDialog
        title: qsTr("Error")
        text: ""
        onAccepted: { /* ダイアログだけ閉じる */ }
    }
}

ポイント:

  • onClicked には分岐や Cursor 走査を書かず、Logic への委譲と結果処理だけにする
  • ロジックへは curScore と UI から集めたオプションを渡す(戻り値でエラー文字列など)

3.4 動作ロジック(logic.js

javascript
// logic.js — 初手は .pragma library なしでよい(呼び出し元 QML の import を継承)
function apply(score, options) {
    if (!score)
        return qsTr("スコアが開かれていません。")

    score.startCmd()
    var cursor = score.newCursor()
    cursor.rewind(Cursor.SELECTION_START)
    while (cursor.segment) {
        var e = cursor.element
        if (e && e.type === Element.CHORD) {
            // … options.skipTies などを参照して処理 …
        }
        cursor.next()
    }
    score.endCmd()
    return ""  // 空文字 = 成功
}

即実行型(UI なし)でも同じ .js を使えます。

qml
import MuseScore 3.0
import "logic.js" as Logic

MuseScore {
    onRun: {
        var err = Logic.apply(curScore, { skipTies: true })
        if (err)
            console.log(err)
        quit()
    }
}

.pragma library について

ファイル先頭に .pragma library を付けると、複数の QML から 共有されるステートレスなライブラリになります。
一方で、呼び出し元 QML の import を継承しません。そのため Element.NOTECursor などを JS 内で使う場合は、QML から値を渡すか、JS 側で明示的な .import が必要です。

純関数のユーティリティ(数値変換など)以外では、初手は pragma なしで問題ありません。

3.5 同階層の UI 部品(.qml

エントリと同じフォルダに置いた .qml は、ファイル名(拡張子なし)が型名になります。import は不要です。

qml
// OptionRow.qml
import QtQuick
import QtQuick.Layouts
import Muse.UiComponents

RowLayout {
    property alias checked: skipTies.checked

    CheckBox {
        id: skipTies
        text: qsTr("タイをスキップ")
        checked: true
        onClicked: checked = !checked
    }
}
qml
// myplugin.qml 内
OptionRow { id: opts }
// opts.checked を optionsFromUi() で渡す

3.6 運用上の注意

項目内容
専用サブフォルダ依存 .js / 子 .qml がある場合は必須に近い。Plugins 直下の単一ファイル配置では相対 import が壊れやすい
.qml の誤検出レガシー検出は .qml 内に MuseScore という語があるとプラグイン扱いすることがある。補助ファイルでは import MuseScore やコメント中のその語を避ける(§7.3
実行経路有効化後、Plugins メニューから実行する。3.x の Plugin Creator ではマルチファイルの再読込が不安定だった報告があるが、4.x では Creator 非搭載のため通常は問題にならない
反映されないときエントリだけでなく .js / 子 .qml の変更も、キャッシュの影響で反映されないことがある。MuseScore の再起動を試す
i18n分割した .qml / .jslupdate の対象に含める(国際化開発helpers.js 例)
即実行型UI 分割は不要。エントリの onRun から Logic.run / Logic.apply を呼ぶだけでよい

3.7 完成テンプレート(ダウンロード)

分割+musescore-plugin-lib+日本語 i18n に加え、§7 の落とし穴対策(quit() / requiresScore: false / Settings / 追加・削除マーカ / フォント検査 / 子 QML 誤検出回避)まで入れた雛形を公開しています。

リソースURL
解説プラグインテンプレート
リポジトリyu1row/musescore-plugin-template
ZIPmain.zip

3.8 5.x Extensions との関係

本章の主対象は 4.x レガシー QML です。5.x の Extensions では、もともと form(QML UI)と macros(JS 動作)が manifest.json で分離されています。詳細は 概要 §2 を参照してください。


4. 独自フォントの利用

手順・ツール・組み込みの詳細: 独自フォント開発ガイド

本節では要点を示します。インストールから配布までの手順は上記ガイドを参照してください。

前提: フォントは OS にインストールして使います。プラグインは、楽譜上に 独自の文字・記号を載せるために、インストール済みフォントのファミリー名を fontFace へ指定します。

項目内容
用途楽譜テキスト(Staff Text 等)への独自文字・記号の挿入
フォント形式全 OS 向けには .ttf(推奨) または .otf
導入開発者・利用者とも OS へのインストールが基本
プラグインnewElement(Element.STAFF_TEXT) 等 + fontFace + 記号文字

4.1 楽譜への組み込み例

qml
import MuseScore 3.0

MuseScore {
    readonly property string symbolFontFace: "MyScoreSymbols" // OS 上のファミリー名
    readonly property string symbolChar: "\uE000"             // フォント内の独自記号

    onRun: {
        if (!curScore) {
            quit()
            return
        }
        curScore.startCmd()
        var el = curScore.selection.elements[0]
        if (el && el.type === Element.NOTE) {
            var text = newElement(Element.STAFF_TEXT)
            text.text = symbolChar
            text.fontFace = symbolFontFace
            text.fontSize = 12
            el.add(text)
        }
        curScore.endCmd()
        quit()
    }
}
  • fontFace には ファイル名ではなくファミリー名を指定します。
  • 独自記号のコードポイントは、フォント側の割り当て(多くの場合 Unicode 私用領域)と一致させます。
  • 利用者が未インストールの場合は欠字や別フォント表示になるため、配布物に インストール手順を必ず含めます。
  • 起動時に Qt.fontFamilies() でインストール済みか検査し、未導入なら警告を出すと安全です(§7.2独自フォント開発 §7.3)。

4.2 形式と配布

形式説明
.ttfWindows / macOS / Linux で扱いやすく、配布の第一候補
.otf同上。OpenType 機能が必要なときに選択

プラグイン zip には fonts/ と OS 別のインストール手順を同梱し、「有効化の前に OS へ入れる」ことを明記します。
音符字形そのものを差し替える SMuFL 記譜フォントは別系統です(詳細は 04 §9)。


5. 国際化(i18n)

手順・ツール・組み込みの詳細: 国際化(i18n)開発ガイド

本節では要点を示します。Linguist / lupdate / lrelease の導入から .qm 同梱、4.x での注意までは上記ガイドを参照してください。

ユーザー向け文字列はハードコードせず、翻訳可能な形で書きます。
公式の概要は docs/old_docs/i18n.md および PluginAPI Docs: Internationalization です。

5.1 方針の選び方

方針方法向いている場合
A. MuseScore 既存訳の再利用qsTranslate("コンテキスト", "原文")音名、メニュー文言など本体に既にある語
B. プラグイン独自翻訳qsTr("…")translations/locale_XX.qmプラグイン固有の説明文・ボタン
A+B の併用両方実用上もっとも多い

例(同梱 note_names):

qml
return qsTranslate("global", "C♯")   // MuseScore 本体の翻訳を利用
qml
StyledTextLabel {
    text: qsTr("Apply lyrics in lilypond format.")
}

5.2 コード側の書き方

qml
// 基本
qsTr("Hello")

// 翻訳者向けコメント(第 2 引数)
qsTr("Run", "verb: execute the plugin")

// プレースホルダ
qsTr("Pages: %1").arg(pageCount)

// 複数形(第 3 引数に個数)
qsTr("%n note(s) selected", "", noteCount)

// メニューパス(先頭の Plugins は翻訳しない)
menuPath: "Plugins." + qsTr("My Plugin")

ダイアログの Apply/Cancel は、同梱例に倣い本体訳を再利用できます。

qml
text: qsTranslate("PrefsDialogBase", "Apply")

5.3 独自翻訳ファイルの用意(手順)

推奨ディレクトリ構成:

myplugin/
  myplugin.qml
  translations/
    locale_ja.ts      # 編集用(Qt Linguist)
    locale_ja.qm      # 実行時に読むコンパイル済み
    locale_de.ts
    locale_de.qm
  1. プラグインを 専用サブフォルダに置く(他プラグインの翻訳と衝突しにくくするため)

  2. translations フォルダを作成する

  3. 文字列抽出(Qt の lupdateXX は言語コード):

    bash
    lupdate myplugin.qml -ts translations/locale_ja.ts
  4. Qt Linguistlocale_ja.ts を翻訳する

  5. リリース(.qm 生成):

    bash
    lrelease translations/locale_ja.ts
  6. MuseScore の表示言語を合わせ、プラグインを実行して確認する

Plugins for 4.x でも、翻訳はプラグインサブフォルダの translations に置くと案内されています。

5.4 MuseScore 4.x での注意(独自 .qm

3.x 向けドキュメントでは、上記の translations/locale_XX.qm を置けば自動で読まれると説明されています。
MuseScore 4.x では、この仕組みが常に有効とは限らず、プラグイン固有の .qm が反映されないことがあります(関連: GitHub Issue #30833)。

推奨する実装方針:

優先内容
1可能な文言は qsTranslate で本体翻訳を再利用する(4.x でも確実)
2独自文言は qsTr 化し、.ts / .qm手順どおり同梱する(ドキュメント上の標準構成および将来の互換のため)
3独自訳が反映されない場合の代替として、プラグイン内に 簡易な言語切替(自前の辞書オブジェクト)を用意する

自前辞書の最小例:

qml
property string uiLang: Qt.locale().name.indexOf("ja") === 0 ? "ja" : "en"
readonly property var trMap: ({
    "ja": { "run": "実行", "cancel": "キャンセル" },
    "en": { "run": "Run", "cancel": "Cancel" }
})
function t(key) {
    return (trMap[uiLang] && trMap[uiLang][key]) || trMap["en"][key] || key
}

大規模な翻訳管理では .ts / .qm を正とし、対象の MuseScore 4.x で表示言語を切り替えて動作を確認してください。


6. import のバージョン指定

QML 先頭の import は、対象 MuseScore/Qt の組み合わせで成否が変わります
MuseScore Studio 4.4 以降は Qt 6のため、旧来のバージョン付き Qt import が原因でプラグインが一覧に出ないことがあります。

6.1 推奨(4.4+ / Qt 6)

qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs

import MuseScore 3.0
import Muse.Ui
import Muse.UiComponents
// ファイル I/O が必要なときのみ:
import FileIO 3.0
モジュール書き方説明
QtQuick など Qt 系バージョンなし(推奨)Qt 6 向け。import QtQuick 2.9 のような旧形式は避ける
QtQuick.Controlsバージョンなし(= Controls 2)Controls 1 は Qt 6 で削除済み
MuseScoreimport MuseScore 3.0(バージョン必須)URI 名は歴史的に 3.0。import MuseScore だけは不可
FileIOimport FileIO 3.0MuseScore が提供するモジュール(Qt 標準ではない)
Muse.Ui / Muse.UiComponentsバージョンなしアプリ同梱 UI。API はソース依存で安定保証は弱い

6.2 やってはいけない/注意が必要な指定

指定問題
import QtQuick.Controls 1.x4.4+ でモジュール不在 → プラグインが認識されないことが多い
import QtQuick.Controls.StylesQt 6 では削除。削除する
import Qt.labs.settings 1.04.4+ では「未インストール」になり得る。Settings は MuseScore 側で使える想定(明示 import を外す)。lilyrics.qml 参照
import MuseScore 1.0MuseScore 2 時代。現行は 3.0
不要な失敗 import1 行でも解決に失敗すると、Plugins 一覧に出ないことがある

6.3 3.x / 4.0–4.3 と 4.4+ を両立させるとき

コミュニティの Plugins for 4.x では、4.4 向けメタプロパティをコメント化し、実行時に mscoreMajorVersion / mscoreMinorVersion でセットする例が紹介されています。

qml
import QtQuick
import MuseScore 3.0

MuseScore {
    // 4.4+ では静的プロパティとして解釈される書き方を併用する場合あり
    // title / thumbnailName / categoryCode は 4.x 向けに付与

    Component.onCompleted: {
        if (mscoreMajorVersion >= 4 && mscoreMinorVersion <= 3) {
            title = "My Plugin"
            thumbnailName = "thumb.png"
            categoryCode = "composing-arranging-tools"
        }
    }
}

新規に 4.4 以降だけを対象にするなら、バージョンなしの Qt import + import MuseScore(または 3.0)+ title / categoryCode 等を素直に書く方が簡単です。
共有ユーティリティ musescore-plugin-lib4.4+ 専用です(4.3 との同一ファイル共存は行いません)。

6.4 エイリアス import

長いモジュール名は as で短縮できます(同梱 tuning.qml):

qml
import Muse.UiComponents as MU

MU.FlatButton { text: qsTr("Apply") }

6.5 開発時の確認手順

  1. 対象の MuseScore 4.x でプラグインを有効化し、一覧に表示されるかを確認する(import 失敗の第一症状)
  2. 出ない場合は %LOCALAPPDATA%\MuseScore\MuseScore4\logs\(Windows)等のログで QML モジュールエラーを確認する
  3. 同梱サンプル(share/plugins/)の import 行をコピーして差分を減らす

公式の 4.4 向け更新メモ: Updating plugins for MuseScore Studio 4.4


7. 実務知見・落とし穴

KalimbaNotation など 4.4+ 向け改修で得た、ドキュメント上まだ薄い/見落としやすい点をまとめます。
詳細手順があるトピックは各ガイドへリンクします。

7.1 多言語対応(独自 .qm が効かない)

MuseScore 4.x では、プラグイン同梱の translations/locale_XX.qm読み込まれないことがあります(Issue #30833)。

方針内容
本体訳の再利用qsTranslate("コンテキスト", "原文")(Apply/Cancel、音名など)
標準構成の維持それでも qsTr.ts / .qm は同梱する(将来・環境差への備え)
代替フォールバックUI 言語が日本語などのとき、埋め込み辞書へフォールバック(qsTr の結果が原文のままなら辞書を使う)

実装の型と手順は 国際化開発 §8 を参照してください。実例: KalimbaNotation の I18n.qml

7.2 フォントインストール済みチェック

独自フォントは OS インストール前提です。未導入のまま実行すると欠字・別フォント化けになります。
起動時(onRun)や Apply 前に Qt.fontFamilies() で検査し、不足があれば警告ダイアログを出すのが実務的です。

qml
function isFontInstalled(family) {
    try {
        var families = Qt.fontFamilies()
        var target = ("" + family).toLowerCase()
        for (var i = 0; i < families.length; i++) {
            if (("" + families[i]).toLowerCase() === target)
                return true
        }
    } catch (e) {}
    return false
}

// onRun 例:
// var missing = []
// if (!isFontInstalled("MyScoreSymbols"))
//     missing.push("MyScoreSymbols")
// if (missing.length)
//     showWarning("未インストール: " + missing.join(", ") + "\nfonts/ から OS へ入れて再起動してください。")
  • インストール直後は MuseScore の再起動が必要なことが多いです。
  • 詳細と配布手順: 独自フォント開発

7.3 分離した QML が別プラグインとして検出される

レガシープラグイン検出は、Plugins 配下の .qml を走査し、内容に MuseScore という語があるとプラグイン候補として扱うことがあります(ルートが MuseScore { } かどうかだけではない)。
そのため、分割した補助 .qml(Helper / I18n / Notation など)が Plugins マネージャに別エントリとして並ぶことがあります。

対策:

やり方説明
補助ファイルに MuseScore と書かないimport MuseScore 3.0 もコメント中の一語表記も避ける
列挙はエントリから注入エントリだけが import MuseScore し、Element / Cursor / Placement などを property var で子へ渡す
アプリ名が必要な文言ソースに一語で書かず、実行時に "Mu" + "seScore" のように組み立てる
利用者への案内README で「有効化するのはエントリ(例: Kalimba Notation)だけ」と明記する
qml
// Helper.qml — MuseScore モジュールを import しない
import QtQuick

Item {
    property var elementTypes   // エントリから Element を渡す
    property var cursorTypes

    function isStaffText(el) {
        return el && el.type === elementTypes.STAFF_TEXT
    }
}

// エントリ側
Helper {
    id: helper
    elementTypes: Element
    cursorTypes: Cursor
}

ロジックを .js に寄せると、この検出問題を避けやすいです(§3)。

7.4 ダイアログを閉じると MuseScore 本体が終了する

終了方法結果
quit()プラグイン(ダイアログ)だけ終了する。推奨
Qt.quit()Qt アプリケーション全体の終了扱いになることがあり、MuseScore 本体まで閉じる

Cancel / Apply / ウィンドウの閉じる操作は、いずれも quit()(または quit() を呼ぶラッパ)に統一します。旧 3.x サンプルやネット上の例に残る Qt.quit() は 4.x では使わないでください。

qml
function closePlugin() {
    quit()  // Qt.quit() は使わない
}

FlatButton {
    text: qsTranslate("PrefsDialogBase", "Cancel")
    onClicked: closePlugin()
}

7.5 全スコア閉鎖時にプラグインを閉じるとアプリが終了する

requiresScore が既定(true)の dialog で、開いているスコアが 0 の状態でプラグインを閉じると、MuseScore 本体まで終了してしまうことがあります。

対策内容
requiresScore: falseスコア無しでも dialog を維持できる。スコア必須処理は onRun / Apply 内で curScore を検査して警告する
終了は常に quit()§7.4 と同じ
qml
MuseScore {
    pluginType: "dialog"
    // 全スコア閉鎖時に dialog を閉じてもアプリごと落ちにくくする
    requiresScore: false

    onRun: {
        if (!curScore) {
            warningDialog.text = qsTr("スコアを開いてから実行してください。")
            warningDialog.open()
        }
    }
}

スコア必須の即実行型では requiresScore: true のままで問題ないことが多いです。dialog で「設定を開いたままスコアを閉じ得る」UI では false を検討してください。

7.6 設定の永続化(Qt.labs.settings が無い)

MuseScore Studio 4.4+(Qt 6) では、import Qt.labs.settings 1.0モジュール未インストールになり、プラグインが一覧に出ない/起動に失敗することがあります。

環境設定の書き方
4.4+import Qt.labs.settings書かないSettings { } を MuseScore 側の型として使う(同梱 lilyrics.qml 参照)
3.x / 一部の旧 4.ximport Qt.labs.settings 1.0 が必要な場合あり
qml
import MuseScore 3.0
// import Qt.labs.settings 1.0  ← Studio 4.4+ では付けない

MuseScore {
    Settings {
        id: backend
        category: "MyPlugin"
        property alias modeIndex: settings.modeIndex
    }
}

スキーマ管理・UI 同期は musescore-plugin-lib の settings.js も利用できます。3.x と 4.4+ を両立する場合は、パッケージを分ける(KalimbaNotation の musescore4/ 方式)のが現実的です。

7.7 プラグインが追加した要素の削除

楽譜に Staff Text などを足すプラグインは、後から自分の成果物だけ消せるように識別子を仕込んでおくと安全です。

手法内容
専用 fontFace独自フォント名で判定(フォント依存の記号向け)
不可視マーカテキスト先頭にゼロ幅スペース(\u200B)などを付ける
削除処理対象範囲を走査し、条件に合う要素を removeElement(または親からの remove
qml
readonly property string marker: "\u200B"
readonly property string myFont: "MyScoreSymbols"

function isMine(el) {
    if (!el || el.type !== Element.STAFF_TEXT)
        return false
    if (el.fontFace === myFont)
        return true
    var t = el.text || ""
    return t.indexOf(marker) === 0
}

// 追加時
text.text = marker + visibleLabel
text.fontFace = myFont

// 削除時(イメージ)
// score.startCmd()
// … cursor / annotations を走査し isMine(el) なら removeElement(el) …
// score.endCmd()

ユーザーが手で書いた同種テキストを誤消ししないよう、フォント名・マーカの両方、またはプラグイン専用の明確な条件で絞ります。実例: KalimbaNotation の削除モード。


8. 参考リンク

内容参照
レガシー QML の構造・メタプロパティプラグイン開発の概要 §3
API 詳細(FileIO・fontFace など)API リファレンス
4.x インストール/有効化Handbook: Plugins
4.x 向け移植・プロパティPlugins for 4.x
プラグインの複数ファイル分割Splitting a plugin into two qml files or more
QML からの JavaScript リソース importQt: Importing JavaScript Resources
4.x デバッグ手段の議論Is there an alternative way to debug plugins in MuseScore 4?
非公式: GUI コンソール代替Plugin Development Console
非公式: ファイルログ部品DebugTools
4.4+ 共有 JS ライブラリmusescore-plugin-libGitHub
分割+lib+日本語の雛形プラグインテンプレートGitHub
4.4 / Qt 6 更新Wiki: Updating plugins for 4.4
独自フォント(詳細手順)独自フォント開発
譜表・記譜操作(数字譜・レイアウト)譜表・記譜操作ガイド
国際化(詳細手順)国際化開発
本体翻訳コンテキスト一覧本体翻訳コンテキスト一覧
翻訳コンパイル自動化翻訳コンパイル自動化
独自 .qm が反映されない場合Issue #30833
実例(4.4+ 改修・本節の知見)KalimbaNotation
同梱 UI サンプルintervals / tuning / lilyrics