外観
開発ノウハウ
本章では、現時点で主流の MuseScore 4.x 向けに、レガシー QML プラグインを効率よく作るための実務的なノウハウをまとめます。
- VSCode による開発環境の構築例
- UI の実装例
- ソース分割(UI・橋渡し・動作)
- 独自フォントの利用 — 詳細は 独自フォント開発
- 国際化(i18n) — 詳細は 国際化開発
- import のバージョン指定
- 実務知見・落とし穴
- 参考リンク
数字譜・譜線非表示・改行・小節幅揃え・音符のテキスト置換などは、別ページ 譜表・記譜操作ガイド にまとめています。
対象:
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):
- MuseScore Studio 4.x をインストールし、起動できることを確認する
- VSCode をインストールする(ユーザーインストーラ/システムインストーラどちらでも可)
- 作業用フォルダを決める(本ワークスペース、またはユーザー Plugins フォルダ直下など)
- 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.gitlens | Git の履歴・注釈をエディタ上で確認する |
コマンドラインからのインストール例:
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手順
Plugins フォルダを用意する
ユーザー Plugins フォルダに、プラグイン専用のサブフォルダを作成します(依存ファイルがある場合は必須に近い運用です)。OS 既定パス Windows C:\Users\<ユーザー名>\Documents\MuseScore4\Plugins\<プラグイン名>\macOS ~/Documents/MuseScore4/Plugins/<プラグイン名>/Linux ~/Documents/MuseScore4/Plugins/<プラグイン名>/実際のパスは Preferences → Folders → Plugins で確認・変更できます。
公式 Handbook: PluginsVSCode で
.qmlを書く
ルートはMuseScore { }、エントリはonRunです。4.x 向けにはtitle/thumbnailName/categoryCodeなども付与します。Hello World の例:
qmlimport 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_names、tuningなど)
MuseScore で有効化する
MuseScore 4.x を起動し、Home → Plugins(または Plugins → Manage plugins…)で対象を Enable します。
初回以降、.qmlを上書きした場合は 再実行、反映されないときは MuseScore の再起動 を行います。スコアを開いて実行する
楽譜を開いた状態で Plugins メニュー(またはカテゴリ付きサブメニュー)から実行します。requiresScoreが既定のままなら、スコアが必要です。結果を確認する(デバッグ)
注(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-lib の log.jsバッファ+ダイアログ/FileIO 4.4+ 向け共有ライブラリ。ユーザー向けは dump、開発向けはwriteFileコミュニティ製コンソール/ログ部品 GUI やファイルへの集約 Plugin Development Console、DebugTools など(非公式) console.log自体は書いておいて問題ありません(デバッグビルドや一部環境では端末に出ます)が、リリース相当の 4.x では「見えない前提」で代替を用意するのが安全です。qmlonRun: { console.log("curScore:", curScore); if (!curScore) { console.log("error: no score"); quit(); return; } curScore.startCmd(); // … 処理 … curScore.endCmd(); quit(); }代替例 A:
FileIOで一時フォルダに追記ログ(API リファレンス §9)qmlimport 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"修正を繰り返す
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 は編集環境として適していますが、次の点はあらかじめ理解してください。
| 項目 | 内容 |
|---|---|
| 実行環境は MuseScore | VSCode の 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 Creator(Ctrl+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 / Cursor | QML の編集、Git、ドキュメント参照、日常開発 |
| Qt Creator(任意) | 一般的な QML 構文の確認。MuseScore プラグインの必須ツールではない |
2. UI の実装例(pluginType: "dialog")
即実行型(pluginType 未指定)は onRun だけで処理して quit() します。
設定画面や確認が必要な場合は pluginType: "dialog" にし、ルート MuseScore の子として UI を置きます。
同梱の実例:
| プラグイン | パス | UI の特徴 |
|---|---|---|
| Mirror Intervals | intervals/mirror-intervals-3.qml | ドロップダウン+Apply/Cancel、MessageDialog |
| Tuning | tuning/tuning.qml | Muse.UiComponents 中心の本格ダイアログ |
| Lilyrics | lilyrics/lilyrics.qml | TextArea、SpinBox、ComboBox、CheckBox |
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 | 用途 |
|---|---|---|
StyledTextLabel | Muse.UiComponents | ラベル・説明文 |
FlatButton | Muse.UiComponents | ボタン(accentButton: true で強調) |
CheckBox | Muse.UiComponents または QtQuick.Controls | オン/オフ |
StyledDropdown | Muse.UiComponents | 一覧からの選択(推奨) |
ComboBox | QtQuick.Controls | 一覧からの選択(標準) |
SpinBox | QtQuick.Controls | 整数の増減 |
IncrementalPropertyControl | Muse.UiComponents | 小数付き数値(tuning で使用) |
TextField / TextArea | QtQuick.Controls | 1 行/複数行テキスト |
RoundedRadioButton / FlatRadioButton | Muse.UiComponents | 択一 |
ラベルとボタン
qml
StyledTextLabel {
text: qsTr("処理対象")
}
FlatButton {
text: qsTr("実行")
toolTipTitle: qsTr("選択範囲に処理を適用します")
onClicked: { /* … */ }
}
FlatButton {
text: qsTranslate("PrefsDialogBase", "Apply")
accentButton: true // 主ボタン
onClicked: { /* … */ }
}チェックボックス
Muse.UiComponents の CheckBox は、クリック時に自分で checked をトグルする書き方が同梱例で多いです。
qml
CheckBox {
id: skipTies
text: qsTr("タイをスキップ")
checked: true
onClicked: checked = !checked
}ドロップダウン(選択)
StyledDropdown(mirror-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].noteComboBox(文字列リスト):
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
}ui は import Muse.Ui で利用できます。
2.3 メッセージダイアログ
エラーや確認には MessageDialog(QtQuick.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 の FileIO(API リファレンス §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 / endCmd | Apply 時の楽譜変更は必ずコマンドで囲む |
ダイアログ中も curScore を参照可能 | ただしユーザーがスコアを閉じた場合などに注意 |
| 見た目の統一 | 可能なら FlatButton / StyledDropdown / StyledTextLabel を使う |
| 即実行と dialog の混在 | UI なし処理だけなら pluginType を付けない方が単純 |
| Qt 6 | import 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.NOTE や Cursor などを 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 / .js も lupdate の対象に含める(国際化開発 の 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 |
| ZIP | main.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 形式と配布
| 形式 | 説明 |
|---|---|
.ttf | Windows / 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プラグインを 専用サブフォルダに置く(他プラグインの翻訳と衝突しにくくするため)
translationsフォルダを作成する文字列抽出(Qt の
lupdate。XXは言語コード):bashlupdate myplugin.qml -ts translations/locale_ja.tsQt Linguist で
locale_ja.tsを翻訳するリリース(
.qm生成):bashlrelease translations/locale_ja.tsMuseScore の表示言語を合わせ、プラグインを実行して確認する
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 で削除済み |
MuseScore | import MuseScore 3.0(バージョン必須) | URI 名は歴史的に 3.0。import MuseScore だけは不可 |
FileIO | import FileIO 3.0 | MuseScore が提供するモジュール(Qt 標準ではない) |
Muse.Ui / Muse.UiComponents | バージョンなし | アプリ同梱 UI。API はソース依存で安定保証は弱い |
6.2 やってはいけない/注意が必要な指定
| 指定 | 問題 |
|---|---|
import QtQuick.Controls 1.x | 4.4+ でモジュール不在 → プラグインが認識されないことが多い |
import QtQuick.Controls.Styles | Qt 6 では削除。削除する |
import Qt.labs.settings 1.0 | 4.4+ では「未インストール」になり得る。Settings は MuseScore 側で使える想定(明示 import を外す)。lilyrics.qml 参照 |
import MuseScore 1.0 | MuseScore 2 時代。現行は 3.0 |
| 不要な失敗 import | 1 行でも解決に失敗すると、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-lib も 4.4+ 専用です(4.3 との同一ファイル共存は行いません)。
6.4 エイリアス import
長いモジュール名は as で短縮できます(同梱 tuning.qml):
qml
import Muse.UiComponents as MU
MU.FlatButton { text: qsTr("Apply") }6.5 開発時の確認手順
- 対象の MuseScore 4.x でプラグインを有効化し、一覧に表示されるかを確認する(import 失敗の第一症状)
- 出ない場合は
%LOCALAPPDATA%\MuseScore\MuseScore4\logs\(Windows)等のログで QML モジュールエラーを確認する - 同梱サンプル(
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.x | import 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 リソース import | Qt: 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-lib(GitHub) |
| 分割+lib+日本語の雛形 | プラグインテンプレート(GitHub) |
| 4.4 / Qt 6 更新 | Wiki: Updating plugins for 4.4 |
| 独自フォント(詳細手順) | 独自フォント開発 |
| 譜表・記譜操作(数字譜・レイアウト) | 譜表・記譜操作ガイド |
| 国際化(詳細手順) | 国際化開発 |
| 本体翻訳コンテキスト一覧 | 本体翻訳コンテキスト一覧 |
| 翻訳コンパイル自動化 | 翻訳コンパイル自動化 |
独自 .qm が反映されない場合 | Issue #30833 |
| 実例(4.4+ 改修・本節の知見) | KalimbaNotation |
| 同梱 UI サンプル | intervals / tuning / lilyrics |