前回の記事 では、番付表用の相撲字風の書体を作りました。今回は、ABC 記法(テキストで楽譜を書く記法)で書いた曲から、五線譜の PDF を作る帳票です。
ネタ記事です。五線譜の実用性はありませんが、計算式の高速化は本気で調べました。
できあがったのは、下の五線譜です。ト音記号・調号・拍子記号・音符・連桁・タイ・加線・歌詞を、計算式だけで組んでいます。記号は書体を使わず、SVG の線で描いています。
この記事の見どころは、次の 3 つです。
- ABC 記法の読み取り・小節の長さの確かめ・段組・連桁・移調を、すべて計算式で行った。ボタンは通常・ドレミ付き・移調の 3 つ(3 章、5 章)
- 楽譜の書体(Bravura)と自作の SVG を比べ、PDF を作るどの PC でも同じに出る SVG を選んだ(4 章)
- 計算式が遅く、長い曲で評価回数の上限を超えた。原因を計算式のエンジンの中まで調べ、関数の呼び出しを正規表現と表引きに置き換えて、約 100 秒を約 10 秒にした(6 章)
元にしたのは、次のような依頼文です(要約)。
印刷屋プラグインで、ABC 記法で書いた曲から「五線譜」の PDF を作る帳票例を作って。
- 曲は、著作権の切れた唱歌・クラシックか自作だけ。原曲の旋律だけを使い、編曲は使わない
- アプリは 1 レコード = 1 曲。曲名・作詞・作曲・拍子・調・テンポ・ABC 記法・歌詞(1 音に 1 音節)。テストレコードは、やさしい 4/4 のハ長調、3/4、調号のある曲、8 分音符・付点・休符・臨時記号の多い曲、小節の長さをわざと間違えた曲
- 単旋律・ト音記号・2/4〜4/4・全音符〜16 分音符・付点・休符・♯♭♮・調号・タイ・加線・拍ごとの連桁・符尾の向き・終止線・歌詞。和音・スラー・強弱・反復は範囲外
- A4 縦。小節の途中で段を切らない。長さに比例した間隔。ボタンは通常・ドレミ付き・移調の 3 つ。長さの合わない小節に印を付け、余白にエラーを書く
- 記号は SVG。Bravura(OFL の楽譜書体)と SVG の線を比べ、1 台の PC で確実に動く方を選ぶ
- レイアウトの前に、ABC → 音符の計算式をエンジンで確かめる。評価回数の上限(10 万回)に注意し、長い曲でも試す
この依頼文を AI に渡すところから始めました。チャットで頼んだことと結果は、次のとおりです。
| # | 頼んだこと | 結果 |
|---|---|---|
| 1 | 上の依頼文 | AI が 4 つ聞いてきた(アプリを作るスペース、ボタンの数、Bravura の比べ方、曲)。スペースは前回と同じ、ボタンは 3 つ、Bravura は PC に入れず手元で比べる、曲はおまかせ、と答えた。アプリ・見本の曲・記号・計算式を作ったが、1 曲 3〜22 秒かかり、長い曲は評価回数の上限を超えた。式が大きくなると「k が無い」エラーも出た |
| 2 | 「なぜ IF と ARRAY_GET が多いのか?」「DIC を使うと簡単では?」「データの持ち方が、配列のネストになっているということか?」「IF がネストになっているのはなぜ? IFS が使えないのか?」「IFS の最後は、そのまま返す値のみ記述で良い」 | AI が、音符 1 つずつ ARRAY_GET と IF で値を取っていること、エンジンが関数の呼び出しのたびに遅くなること、ARRAY_GET が式全体で 100 個を超えると「k が無い」になることを説明した |
| 3 | 「SWITCH・IFS・計算・表の文字列で IF と ARRAY_GET を減らし、k が無いエラーも確かめながら続けて」 | 字句ごとの値を、表を付けた正規表現 1 回で引く形に組み直した。連桁は正規表現と SVG の clipPath にした。長い曲が約 100 秒 → 約 10 秒、評価回数は上限の 4 割弱。作業フォルダーに保存し、kintone に見本 6 件を入れて、プレビューの式のエラー 0 を確かめた |
| 4 | (実機の PDF 14 本を見せる) | 手元で作った画像と画素で比べ、同じに描かれていることを確かめた |
| 5 | 「Qiita 記事にして」 | この記事 |
使ったのは、印刷屋プラグイン Ver.8、Claude Code(Claude デスクトップアプリの Code タブ)、印刷屋 MCP 0.2.0(資料・検査・プレビュー。中核は tools 2.1.0)です。アプリの作成とレコードの登録は、kintone の MCP サーバーで AI が行いました。
楽譜の用語(簡単に)
この記事に出てくる楽譜の用語です。
| 用語 | 意味 |
|---|---|
| 五線・段 | 音の高さを表す 5 本の線。1 行ぶんの五線を「段」と呼ぶ。段組は、曲を段に割り振って並べること(この帳票では、小節の途中で段を切らない) |
| ト音記号 | 段の左端の渦巻きの記号。第 2 線がト(ソ)の音であることを表す |
| 小節・小節線 | 拍子で区切った 1 区切りと、その区切りの縦線。曲の終わりは終止線(細い線と太い線) |
| 拍子 | 1 小節に入る拍の数。4/4 は「4 分音符を 1 拍として 4 拍」 |
| 音符の長さ | 全音符・2 分・4 分・8 分・16 分音符の順に、半分ずつ短くなる。付点は 1.5 倍。休符は音を出さない長さ |
| 符頭・符尾・旗 | 音符の丸い部分・縦の棒・棒の先の旗(8 分は 1 本、16 分は 2 本)。符尾は上向きと下向きがある |
| 連桁 | 8 分音符や 16 分音符が続くとき、旗の代わりに符尾どうしをつなぐ太い横線。この帳票では 1 拍ごとにまとめる |
| タイ | 同じ高さの 2 つの音をつなぐ弧。1 つの長い音として演奏する(違う高さの音をつなぐ弧はスラーで、この帳票では描かない) |
| 加線 | 五線の上・下にはみ出す音のために足す短い線(下第 1 線は、五線の下に足す 1 本目) |
| 臨時記号 | 音符の前に付ける ♯(半音上げる)・♭(半音下げる)・♮(元に戻す)。同じ小節の、同じ高さの後ろの音にも効く |
| 調・調号 | 調は、曲の中心になる音と音階(ハ長調・ホ短調など)。調号は段の頭に並べる ♯・♭ で、その調でいつも上げ下げする音を表す |
| 移調 | 音どうしの高さの関係を変えずに、曲全体を別の調に移すこと(歌いやすい高さにするときなど)。調号を書き換え、音符を上下に動かす |
| 音名(ドレミ) | 音の名前。この帳票のドレミ付きは、調号と臨時記号を合わせた実際の音(シ♭ など)を出す |
| 単旋律・和音 | 同時に 1 つの音だけが鳴る旋律と、同時に鳴る複数の音。この帳票は単旋律だけ |
| 弱起 | 曲が小節の途中から始まること(最初の小節が短い) |
| 字句 | この記事での言い方。ABC 記法を区切った 1 つずつ(音符・休符・小節線) |
1. アプリと見本の曲
アプリは「五線譜(ABC 記法)」です。1 レコードが 1 曲です。
| フィールドコード | 型 | 中身 |
|---|---|---|
曲名・作詞・作曲
|
文字列 1 行(40 字まで) | 帳票の上に出す |
拍子 |
ドロップダウン | 2/4・3/4・4/4 |
調 |
ドロップダウン | 「C ハ長調」「Em ホ短調」など 18 |
テンポ |
文字列 1 行 | ♩ = 96 など(表示だけ) |
ABC記法 |
文字列(複数行) | 曲の本体 |
歌詞 |
文字列(複数行) | 1 音に 1 音節。空行で 1 番・2 番を分ける(3 番まで) |
移調先 |
ドロップダウン | 「G(ト長調・ホ短調)」など 9。移調のボタンで使う |
権利メモ |
文字列(複数行) | 曲の権利と、旋律を確かめた資料 |
楽譜PDF |
添付ファイル | PDF の保存先 |
喜びの歌のレコードの画面です。上の 3 つが印刷屋のボタンで、作った PDF は「楽譜PDF」に保存されます(3 つのボタンで作った後)。
見本の曲は 6 件です。
| # | 曲 | 拍子・調 | 確かめること |
|---|---|---|---|
| 1 | きらきら星(作詞 Jane Taylor、フランス民謡) | 4/4 ハ長調 | やさしい曲。英語の歌詞(ハイフン) |
| 2 | ふるさと(高野辰之・岡野貞一) | 3/4 ヘ長調 | 3/4。歌詞 3 番。「ー」の伸ばし |
| 3 | 喜びの歌(交響曲第 9 番より。シラー・ベートーヴェン) | 4/4 ニ長調 | 調号。小節と段をまたぐタイ。下の加線。ドイツ語の歌詞と、1 音節を 2 音にのばすところ |
| 4 | 小さな練習曲(自作) | 2/4 ホ短調 | 16 分音符・付点・休符・臨時記号・上の加線・タイ。歌詞なし |
| 5 | 春が来た(エラー確認用。高野辰之・岡野貞一) | 4/4 ハ長調 | 長い小節・短い小節・読めない字。スラーと反復記号 |
| 6 | 音階の練習(96 小節・評価回数の確認用。自作) | 4/4 ハ長調 | 長い曲 |
旋律と歌詞の当て方は、Wikipedia の楽譜(LilyPond)で確かめました。自作の 2 曲のほかは、どれも作詞者・作曲者の著作権の保護期間が終わっています。日本語の訳詞(喜びの歌)や、日本語の歌詞(きらきら星)は保護期間中のものがあるので使っていません。理由は各レコードの「権利メモ」に書きました。
2. ABC 記法の読み方
ABC 記法は、C D E F G A B c のような字で旋律を書く記法です。この帳票で読む範囲は、次のとおりです。
| 書き方 | 意味 |
|---|---|
C D E F G A B |
1 点ハ〜1 点ロ(ト音記号の下第 1 線のド〜第 3 線のシ) |
c d e f g a b |
2 点ハ〜2 点ロ |
' ,
|
1 オクターブ上・下(重ねてよい: c''、C,,) |
^ _ = ^^ __
|
♯、♭、♮、ダブル♯、ダブル♭。同じ小節の、同じ高さの後ろの音にも効く |
z |
休符 |
| 長さ | 基本は 8 分音符。C2 4 分、C3 付点 4 分、C4 2 分、C6 付点 2 分、C8 全音符、C/ 16 分、C3/2 付点 8 分 |
- |
タイ(次の同じ高さの音へ。小節線・段をまたいでよい) |
| || |]
|
小節線、複縦線、終止線 |
|: :|
|
反復記号。ふつうの小節線で描き、注記を出す |
( ) "…"
|
スラー、コード名。描かない(注記を出す) |
% |
行の終わりまでコメント |
拍子と調は、M: K: の行ではなく、アプリの項目から取ります。調から調号を決め、ABC の音名に ♯・♭ を足します。
歌詞は、英字なら空白とハイフン(Twin-kle)で音節に分け、_ で前の音節をのばします。日本語は 1 字 1 音(小さい ゃゅょっ は前の字に付け、「ー」も 1 音)です。
例: 喜びの歌の最初の 4 小節
F2 F2 G2 A2 | A2 G2 F2 E2 | D2 D2 E2 F2 | F3 E E4 |
3. 帳票の組み方
3.1 3 つのボタン
ボタンは「五線譜」「五線譜(ドレミ付き)」「五線譜(移調)」の 3 つです。ドレミ付きは音符の下に音名(シ♭ など)を出します。移調は、移調先の調号に書き換え、書いた臨時記号も書き直します。
移調は、近い方(半音 6 つまで)に移します。ただし、移すと音の平均が五線の外に出るときは、真ん中に近い方にします。最初は「真ん中に近い方」だけで決めていたので、ニ長調 → ハ長調が「半音 10 上」になりました。
3.2 段と間隔
- 横の間隔は、音の長さの √ に比例させた(4 分音符で 50px)。英字の歌詞は、音節の幅(1 字 11px)で広げる
- 段は小節の途中で切らない。小節の幅を 4px で 1 字にした文字列を作り、「段の幅までの最長の一致」の正規表現で切る。段の数を出してから幅をならして切り直し、最後の段に 1 小節だけ残らないようにした
- 最後の段は、6 割より短ければ 1.35 倍まで、それ以上なら端まで伸ばす
3.3 連桁・符尾・タイ
- 連桁は 1 拍(4 分音符ぶん)の中で続く 8 分以下の音。16 分の 2 本目は、続くところは次の音まで、1 つだけなら短い棒
- 符尾の向きは、まとまりの中で第 3 線から遠い音で決める。連桁の傾きは両端の高さの差で、最大 8px。内側の音が飛び出すなら水平
- タイは弧(SVG の
Aコマンド)。符尾が上なら下に、下なら上に。段をまたぐときは、段の終わりまでと、次の段の頭から
3.4 歌詞
歌詞のハイフンは、前の音節と次の音節のあいだの真ん中に置きます。最初は音符の中心どうしの真ん中に置いたので、「a -bove」のように長い音節にくっつきました。計算式では字の幅を測れないので、次のような HTML にしました。
<span class='hy' style='left:120px;width:64px'><i>a</i><b>-</b><i>bove</i></span>
.ms-sys .hy { display: flex; }
.ms-sys .hy i { visibility: hidden; font-size: 50%; } /* 半分の大きさの見えない字 = 音節の半分の幅 */
.ms-sys .hy b { flex: 1; text-align: center; }
箱は前の音符から次の音符まで(音節の中心から中心まで)。半分の大きさの見えない字で両側の音節の半分ずつをよけると、残りの真ん中が音節のあいだの真ん中になります。
3.5 エラーの印
拍子と長さの合わない小節には赤い帯と「! 番号」を付け、1 ページ目の上の赤い枠に内容を書きます。描かない記号(スラー・コード名・反復記号)は、灰色の「注記」に出します。
長い曲は、96 小節・400 字句(音符・休符・小節線)までで打ち切り、注記に「長い曲なので、先頭の 51 小節だけにしています(全体は 96 小節)」と出します。
4. 記号は SVG の線(Bravura と比べて)
楽譜の記号を書体で描くなら、Bravura(SMuFL の楽譜書体、SIL Open Font License 1.1)が定番です。PC には入れず、作業用のフォルダーに落として、自作の SVG の記号と並べて描き比べました。上の段は左が Bravura・右が自作、下の段は重ねたものです。
ト音記号は、方眼の上で Bravura と見比べて高さと幅の比率をそろえ、中心の線(Catmull-Rom 曲線)に太さを付けた輪郭で描きました(形は自作)。
選んだのは SVG のパスです。理由は次の 3 つです。
- 印刷屋の PDF は、帳票の HTML を html2canvas で画像にしたものです。記号の SVG は
<img src='data:image/svg+xml,…'>で描くので、SVG の中の字は PDF を作る PC に入っている書体でしか出ません(ブラウザーは、img で描く SVG から外のファイルを読まないので、Web フォントは使えません)。Bravura で描くなら、PDF を作るすべての PC に Bravura を入れることになります。入っていない PC では、記号(私用領域 U+E000〜 の字)が出ません - 書体を data URL にして SVG に埋め込む手は、Bravura.otf が 889 KB あり、設定の保存の上限(256 KiB)に入りません
- SVG のパスなら、どの PC でも同じ形で出ます。印刷屋のプレビューとも同じです
不利な点は、形を自分で描く手間です。重ねると、ト音記号・休符・旗は Bravura ほど整っていません。
5. 計算式の作り
5.1 流れ
1 つのボタンは、帳票の行 2 つでできています。
| 行 | 計算式 | HTML 欄 |
|---|---|---|
| 1 ページ目の題と注記 | 題・作詞・作曲・テンポ、エラーと注記の枠 | 式が読む定数の JSON(正規表現と表) |
| 五線(全ページ) | 段ごとに 1 枚の SVG と、歌詞・ドレミの HTML | 式が読む定数の JSON(記号の SVG・正規表現・表) |
定数は HTML 欄に JSON で置き、式では JSON_P($html, 0) で読みます。計算式の欄は 20,000 字までなので、長い正規表現や記号の SVG を式から外に出しました。
五線の行の式の流れは、次のとおりです。
- ABC を整える(コメント・ヘッダー・スラーを消す。
',を><に) - タイの印と、書いた臨時記号の印を付ける。同じ小節の臨時記号を後ろの音に写す(後ろ読みの正規表現 1 回)。調号を足す
- 字句に分け、字句ごとの値(種類・長さ・高さ・符尾の向き…)を表で引く(5.2)
- 長さ・小節・エラー、横の位置、段とページ
- 連桁のまとまりと形(5.3)
- 段ごとに x を入れ、タイ・記号・連桁を正規表現で SVG にする(5.4)
小さな曲で途中の値を追った例は、5.5 にあります。
5.2 表を付けた正規表現 1 回で、字句ごとの値を引く
最初の版は、音符 1 つずつ ARRAY_GET と IF で値を取っていました。これを、字句を「印キー」の並びにして、後ろに表を付け、正規表現 1 回で全部引く形にしました。
字句の型: '‖{K$4$8$9}@X|{Y$4$5}|{S$4$5}…{H$6#$4$8}{T$1$7}',
表: §;kC,1;KC,N;lC,1;dC,28;YC,114;SC,~P0~L28~S0:-42:;…;H2,~H4~D4;…
引く: REPLACERE(CONCAT(字句の並び, 表), "\{([A-Za-z])([^}]*)\}(?=[\s\S]*§[\s\S]*;\1\2,([^;]*);)", "g", "$3")
{YC} は「C の y」、{H2} は「長さ 2 の符頭の印」です。先読み((?=…))の中で表の ;YC, を探し、その値に置き換えます。1,000 か所でも、関数の呼び出しは約 20 回で済みます。
5.3 連桁のまとまりを正規表現で作る
連桁は「1 拍の中で続く 8 分以下の音」です。字句ごとに拍の位置を計算すると関数の呼び出しが増えるので、長さを字の並びにして、正規表現で拍の境目を入れました。
- 字句ごとに、8 分以下の音は
b、それ以外はn、小節線はk。16 分音符 1 つぶんにつき 1 字(続きはo)。例: 8 分 2 つと 4 分はbobonooo - 小節の頭から 4 字(1 拍)ごとの位置に
/を入れる(⟦は字句の拍の部分の頭の印)
REPLACERE(拍の文字列, "(?=⟦|o)(?<=(?:^|k)(?:(?:[^bnok]*[bno]){4})*[^bnok]*)", "g", "/")
-
/をまたぐ短い音は連桁にしない。続く短い音(2 つ以上)が 1 つのまとまり - 連桁の形(向き・傾き・高さ)は、まとまりの音の高さの並びだけで決まる。そこで、まとまりの頭に「音の高さの並び」(署名)を付け、
DISTINCTで同じ署名は 1 回だけ計算する
5.4 足し算は SVG にやらせる
連桁の音の符尾は、連桁の線まで伸ばします。線が傾いているので、本来は音ごとに「その x での線の高さ」を計算します。これを計算式でしないために、符尾は長く引いて、連桁の線から音符の側だけを残す clipPath で切りました。
<path id='mu' class='bm' transform='translate(6.7 0)' d='M120 40L190 48'/> <!-- 連桁の中心線(太い線) -->
<clipPath id='qu'><path transform='translate(6.7 0)' d='M120 40L190 48h3V400H120h-3V40z'/></clipPath>
<path class='s' clip-path='url(#qu)' d='M150 90m6.7 -1V-60'/> <!-- 符尾: 音符から上へ長く引く -->
<use href='#mu' transform='translate(0 9)' clip-path='url(#wu)'/> <!-- 16 分の 2 本目: 同じ線を 9px ずらし、間の x だけ残す -->
連桁の線・切る形・2 本目の部品は、字句の後ろに置いて、段ごとに種類(上向き・下向き)で集めます。正規表現は数を足せませんが、パスの相対コマンド(m6.7 -1)と transform で位置をずらせます。
5.5 例で追う: 2 小節が SVG になるまで
4/4・ト長調の 2 小節 B/c/d/B/ A2 ^G4- | G2 z2 G3 F |] が、五線の行の式の中でどう変わるかを、途中の値で追います。手元のエンジンで、式の変数の値を取り出しました。16 分音符 4 つの連桁、臨時記号、小節をまたぐタイ、休符、付点、調号(F♯)が入っています。
① 臨時記号を文字列のまま解く
ABC B/c/d/B/ A2 ^G4- | G2 z2 G3 F |]
a6 B/c/d/B/ A2 ^!G4- | @G2 z2 G3 F |] 書いた臨時記号に !、タイで入る音に @
a7 … ^!G4- | @^G2 z2 G3 F |] タイの先に臨時記号を写す(小節線をまたいでも)
a8 … ^!G4- | @^G2 z2 ^G3 F |] 同じ小節の、同じ高さの後ろの音に写す
a9 … ^!G4- | @^G2 z2 ^G3 ^F |] 調号(F♯)を、臨時記号の無い F に付ける
「♯ はその小節の終わりまで効く」という楽譜の決まりを、音ごとに関数を呼ばず、正規表現で全部の音に一度に当てます。
② 字句ごとの値(表で引く。5.2)
種類 1,1,1,1,1,1,3,1,2,1,1,3 1 音符・2 休符・3 小節線
長さ 1,1,1,1,4,8,0,4,4,6,2,0 16 分音符 = 1(4 分 = 4、付点 4 分 = 6)
歌詞 1,1,1,1,1,1,0,0,0,1,1,0 歌詞を当てる音(タイで入る音・休符・小節線は 0)
③ 小節・横の間隔・段
小節 字句の数 [7, 5]、長さ [16, 16](4/4 は 16 なのでエラーなし)
空き [前, 後ろ] = [0,32] [0,32] [0,32] [0,32] [0,50] [15,70.7] [0,16] …
x の素 10000, 10032, 10064, 10096, 10128, 10193, …
段 小節の幅を 4px で x 1 字にした「xxx…x|xxx…x|」を、段の幅までの最長の一致で切る → 1 段
- 後ろの空きは長さの √ に比例させます(4 分音符で 50px)。書いた ♯ の音は、前を 15px あけます
- x の素は前までの空きの合計です(0 を避けるために 1 万を足しています)
- 例は 1 段に収まり、最後の段なので 1.35 倍まで横に伸ばします
④ 字句ごとの描く印(表で引く)
'‖N@X|78|~P0~L34~S1:42:⟦34b⟧~A0:0~H1~D1'
| 印 | 意味 |
|---|---|
‖N @X 78
|
音符、x(段ごとに入れる)、y |
~P0 ~L34
|
付点の位置(線の上)、段(加線が要るか) |
~S1:42: |
符尾(下向き、長さ 42) |
⟦34b⟧ |
連桁用(段 34、8 分以下の音) |
~A0:0 ~H1 ~D1
|
臨時記号(書いていない)、符頭(16 分)、長さ |
~T1 ~T2
|
タイの始まり、タイで入る音 |
⑤ 連桁(5.3)
拍の字 ⟦34b⟧⟦35b⟧⟦36b⟧⟦34b⟧⟦33nooo⟧⟦32nooooooo⟧⟦0k⟧… 16 分 1 つに 1 字(続きは o、小節線は k)
まとまり ⟦F{G34c35c36c34c}⟧⟦M{…}⟧⟦M{…}⟧⟦M{…}⟧⟦N⟧⟦N⟧⟦N⟧… 同じ拍の短い音: 頭 F・続き M と署名(c は 16 分)
形の表 G34c35c36c34c,1111_111 署名ごとに 1 回(DISTINCT)。下向き、中心線の両端の y は 111
⑥ 段ごとに SVG にする(5.4)
段の左端 + (x の素 − 段の左端の素) × 伸ばす率 で x を入れ、印を正規表現で SVG の要素にします(印の種類ごとに 1 回、42 回)。
<g transform='translate(211 78)'><use href='#l34'/><use href='#h0'/></g> <!-- 1 つ目の B: 加線の部品と黒い符頭 -->
<path class='s' clip-path='url(#qd)' d='M211 78m-6.7 1V400'/> <!-- 連桁の音の符尾: 長く引いて clipPath で切る -->
<g transform='translate(471.6 90)'><use href='#l32'/><path class='s' d='M6.7 -1V-42'/><use href='#a1' x='-17'/><use href='#h1'/></g> <!-- ^G: 符尾・♯・白い符頭 -->
<g transform='translate(0 90)'><path d='M471.6 8A1 0.17 0 0 0 588.6 8A1 0.11 0 0 1 471.6 8z'/></g> <!-- タイの弧(次の小節の G まで) -->
段ごとの SVG に五線・ト音記号・調号・拍子記号を足し、<img src='data:image/svg+xml,…'> で置きます。歌詞は、歌詞を当てる音の x に合わせた HTML の <span> です(タイで入る G と休符には当てません)。
臨時記号・値の表引き・連桁・記号の SVG は、字句ごとに関数を呼ばず、正規表現でまとめて処理しています(五線の行の式に REPLACERE が約 150 個あるのはこのためです)。字句ごとに関数を呼ぶのは、x の計算や歌詞の位置など、数か所だけです。
6. つまずき(印刷屋 Ver.8 の計算式エンジンの性質)
この章は、印刷屋 Ver.8 の計算式エンジンのコードを読み、試して調べたことです。版が変われば変わることがあります。
6.1 「k が無い」エラー: 101 個目の ARRAY_GET は、囲む関数より先に評価される
式が大きくなったところで、次のエラーが出ました。
getVal FIELD CODE NOT FOUND: k
k は ARRAY_FOR(n, k, …) のループの変数です。エンジンのコードを読み、小さな式で試すと、次のことがわかりました。
- エンジンは評価の前に、関数の一覧の順に、1 つの名前につき左から 100 個までの呼び出しを取り出しておく
- 101 個目からは字のまま残る。字のまま残った呼び出しは、それを囲む
ARRAY_FORなどを評価するとき、ループが始まる前に先に評価される - そのため、101 個目がループの変数を読むと、このエラーになる
100 という数は、配列・ループ・変数が無く、複雑な式を想定していなかった計算式ライブラリの初期の版の値が、そのまま残ったものです。
試した式です。前に ARRAY_GET(a, 0) を 100 個置き、その後ろに 101 個目を置きました。
| 101 個目 | 結果 |
|---|---|
ARRAY_FOR(3, k, ARRAY_GET(a, k)) |
エラー(k) |
ARRAY_MAP(a, x, i, ARRAY_GET(a, i)) |
エラー(i) |
ループの中の IF(k < 3, ARRAY_GET(a, k), "-")
|
エラー(k。IF の中に置いても、IF より先に評価される) |
ループの中の LET(m, k, ARRAY_GET(a, m))
|
エラー(m) |
CONCAT(LET(m, 2, ARRAY_GET(a, m))) |
エラー(m。ループが無くても起きる) |
式の一番外側の LET で決めた変数、定数の添字 |
動く |
ループの中の DIC_ITEM(DIC("p", ARRAY_GET(a, k)), "p")
|
動く |
RAND() を 100 個置いた後、ループの中の RAND()
|
エラーは出ないが、5 回とも同じ値 |
数え方は次のとおりです。
- 数えるのは 1 つの計算式の字面で、名前ごとに数える
- 関係するのは、関数の一覧で
ARRAY_FORより前にある関数(ARRAY_GET・DIC_ITEM・DIC・ARRAY・ARRAY_COUNT・RANDなど) -
IF・SWITCH・CONCAT・LEFTなど、一覧でループより後ろの関数は、ループの中を評価するたびに数え直す。ループの中なら 100 個を超えても平気(LEFT("abc", k)を 101 個並べても動いた) -
DIC・RECS_MAP・TABLE_MAPなど一覧で前の関数の呼び出しの中は、外では数えない。その関数を評価するときに、中を別に数える
直し方は 2 つです。
- 1 音ずつの
ARRAY_GETを、5.2 の表引きに置き換えて減らす(いまは 49〜51 個) - 段ごとに描く大きな部分は
DIC_ITEM(DIC("p", 式), "p")で包む。包んだ中は、DICを評価するときに別に数えられる(中は 23 個)
6.2 遅さは、関数の呼び出し回数の 2 乗で増える
最初の版は、見本の曲で 1 曲 3〜22 秒かかりました。エンジンのコードを読むと、関数を呼ぶたびに値の表に 2 つ足し、足すたびに表の大きさを数えていました(Object.keys(…).length)。表は帳票の行をまたいで引き継がれます。この版で測ると、時間は呼び出し回数の 2 乗で増え、IF や MAX も 1 回 0.25 ms ほどかかります。一方、算数と変数を読むのは表を増やしません。
そこで、音ごとの関数の呼び出しを 1 つずつ無くしました。長い曲 2 つ(64 小節と 96 小節。どちらも 400 字句で打ち切り)の、五線の行の評価回数と時間です。
| 版 | 評価回数 | 時間 |
|---|---|---|
| 表引きにした版(連桁はまだ音ごとに計算) | 13.5 万回(上限を外して測った) | 約 100 秒 |
| 連桁を正規表現と clipPath に | 6.4〜9.6 万回 | 19〜48 秒 |
| 移調を表で引く(移調のボタン) | 8.2 万回 | 31 秒 |
| 連桁の形を署名ごとに 1 回(いま) | 3.4〜3.8 万回 | 7〜10 秒 |
見本の曲は 0.5〜3.7 秒です。
| 曲 | 通常 | 移調 |
|---|---|---|
| きらきら星 | 1.7 秒・1.4 万回 | 2.0 秒・1.5 万回 |
| ふるさと | 0.9 秒・1.0 万回 | 1.1 秒・1.1 万回 |
| 喜びの歌 | 3.3 秒・1.9 万回 | 3.7 秒・2.0 万回 |
| 小さな練習曲 | 0.7 秒・1.0 万回 | 1.0 秒・1.1 万回 |
| 春が来た | 0.5 秒・0.6 万回 | 0.7 秒・0.7 万回 |
測るのには、印刷屋の zip に入っている計算式エンジンを Node で読み込み、評価回数と関数の呼び出し数を数える道具を、AI が作って使いました。英字の歌詞の曲(きらきら星・喜びの歌)が遅いのは、歌詞の幅を字句ごとに見ているためです。
6.3 そのほか
-
JavaScript の正規表現の癖: 空に一致する繰り返しの回は捨てられるので、
(?:(?=…(…)))?の中の取り出しは空になる。先読みの中で字を進める形にした -
消えた音符・符尾: 16 分の 2 本目の置き換えで元の字句(
$1)を付け忘れて音符ごと消えた。拍の区切りの/を消し忘れて符尾と連桁が消えた。どちらも、印刷屋と同じ html2canvas で描いた画像で AI が気づいた - 低い音が切れる: 移調で加線 3 本の低い音が、段の SVG の下で切れた。SVG だけ段より 40px 高くした
-
設定の大きさ: 定数の JSON を 3 ボタン × 2 行に置くので、設定は最初 252 KB になった(保存のツールは 1 回 256 KiB まで)。行の式が読む定数と、その行が引く表の分だけに絞って 168 KB にした(出力は 1 字も変わらないことを確かめた。保存すると派生値が足されて 253 KB)。保存は、1 つ目のボタンを
pcraft_save_settings、2・3 つ目をpcraft_update_buttonで足した - ARRAY_MAP の変数が 0 だと空になる: 位置に 1 万を足して 0 を避けた
7. 実機の PDF
印刷屋の画面(Ver.8)で 14 本の PDF を作りました(きらきら星・ふるさと・喜びの歌・小さな練習曲は 3 ボタンとも、春が来たと音階の練習は通常だけ)。どれも A4 縦 1 ページ(音階の練習は 2 ページ)、画像は 2480×3508(150 dpi の 2 倍)の JPEG、1 本 140〜250 KB(2 ページは 600 KB)です。
ここまでの章に載せていない曲の PDF です(下の余白を切り、半分の大きさにしています)。
手元で、同じ設定とレコードから同じ html2canvas 1.4.1(scale 2)で描いた画像と、画素で比べました。
- 枠の無いページは、差の平均が 0.3〜0.55(JPEG の圧縮のぶん)。英字の歌詞の多い喜びの歌は 0.7〜1.0 で、ドレミの小さな字が 1px ずれたところだけ
- エラー・注記の枠のあるページは 2.3〜2.9 で、差は題の太い明朝の縁と、枠より下の段が 1px 上下にずれたところだけ
- 形の違いはなかった
これで確かめたのは手元と実機の描画が同じことまでで、楽譜として正しいか(音の高さ・調号・移調・タイ)は、描いた画像を見て確かめただけです(ほかの楽譜ソフトの出力とは比べていません)。
8. 対応していないこと・気を付けること
- 和音・2 声・大譜表・スラー・強弱・反復(ふつうの小節線で描く)・3 連符・32 分音符・装飾音は描かない
- 拍子は 2/4・3/4・4/4 だけ(6/8 などの拍のまとめ方は無い)
- 最初の小節が短いときは弱起とみなし、エラーにしない(小節番号は次の小節から 1。最後の小節が短くてもエラーにしない)
- 長い曲は 96 小節・400 字句で打ち切る
- 低い音・高い音が多いと、加線が歌詞の行に近づく(段の SVG は 40px までは切らない)
- 題・歌詞の書体は、ヒラギノ・游ゴシック・メイリオの順。Windows の PC では確かめていない
- 曲の権利は、使う人が確かめる。旋律が古くても、編曲・訳詞・新しい歌詞には権利がある
9. 設定ファイル
設定ファイルは 3 ボタンで 253 KB あり、記事に載せるには大きいので、GitHub のサンプルに置きました(項目定義と見本のレコードも)。
印刷屋の設定画面のアップロードで取り込めます(アプリ番号は読み替えてください)。項目のフィールドコードは、1 章の表と同じにします。
気を付ける点
- 1 要素ごとに関数を呼ぶ式は、要素が数百個になると、評価回数の上限より先に時間が問題になる。値は「字句の並び + 表」を正規表現 1 回で引き、組み分けは字の並びと正規表現で作る
-
ARRAY_GET・DIC_ITEMなどは、1 つの式で 1 つの名前につき左から 100 個まで。101 個目からはループより先に評価され、ループの変数を読むと「getVal FIELD CODE NOT FOUND: k」(RANDはループの全回で同じ値)。大きな部分はDIC_ITEM(DIC("p", 式), "p")で包む - 同じ形の計算は
DISTINCTで 1 回に。位置の足し算は SVG(clipPath、transform、パスの相対コマンド)にやらせる - 記号を書体で描くなら、PDF を作るすべての PC にその書体が要る。どの PC でも同じに出すなら SVG のパス
- 長い正規表現や SVG は、HTML 欄に JSON で置き、
JSON_P($html, 0)で読む(計算式の欄は 20,000 字まで)
まとめ
- ABC 記法の曲から、五線譜の PDF(A4 縦)を作る帳票を作った。ボタンは通常・ドレミ付き・移調の 3 つ。小節の長さの合わない曲は、赤い帯と余白の文で知らせる
- 記号は Bravura と比べたうえで、どの PC でも同じに出る SVG のパスにした
- 計算式が遅く、長い曲で上限を超えた。エンジンを読んで「呼び出し回数の 2 乗で遅くなる」「ARRAY_GET などは 101 個目からループの変数を読めない」と突き止め、表引きの正規表現・拍の正規表現・SVG の clipPath に置き換えて、約 100 秒を約 10 秒にした
- 実機の PDF 14 本は、手元の描画と同じだった
- 設計・計算式・確かめ・不具合の調査と直しは AI に任せた。人が行ったのは、作りたいものを頼むこと、式の書き方に疑問を出すこと、実機の PDF を見せることだった
シリーズ「rex0220 印刷屋プラグイン - AI(Claude Desktop)に〜」:
- チャットで帳票を作らせる
- 帳票のデザインを作り込ませる
- 見積書から注文書・請求書を作らせる
- 組織図を設計させてから、アプリを作らせる
- QRコード付きの資産ラベルを作らせる
- 写真付きの点検報告書を作らせる
- ガントチャートアプリの工程表を作らせる(関連レコード一覧に対応した Ver.8)
- 休日アプリから年間カレンダーを作らせる
- 作文を原稿用紙(400 字詰め)の PDF にさせる
- 「あなたが生まれた日の太陽系」カードを作らせる
- 業務データから「番付表」を作らせる
- 番付表用の相撲字風の書体を作らせる
- ABC 記法の曲から「五線譜」を作らせる(この記事)
- 将棋の棋譜から「盤面図」を作らせる
関連リンク:









