VexFlow 入門その1
VexFlow 使い方へ戻る。
概要
ひとつひとつの構成要素を知る前に入門用の楽譜をいくつか描いて全貌をつかもうとするのが最初です。公式サイトにもあるものと同じです。
五線譜
五線譜を描いてみます。目安になる線を引きました。左上の角の頂点が(x, y)対応で(1, 1)、そこから時計回りに(499, 1)→(499, 199)→(410, 100)→(10, 40)という線です。
スクリプトは以下のとおり。
<div id="output"></div>
<script src="https://cdn.jsdelivr.net/npm/vexflow@4.1.0/build/cjs/vexflow.js"></script>
<script>
const {
Renderer,
Stave
} = Vex.Flow;
// Create an SVG renderer and attach it to the DIV element named "boo".
const div = document.getElementById('output');
const renderer = new Renderer(div, Renderer.Backends.SVG);
// Configure the rendering context.
renderer.resize(500, 200);
const context = renderer.getContext();
// Create a stave of width 400 at position 10, 40 on the canvas.
const stave = new Stave(10, 40, 400);
// Add a clef and time signature.
stave.addClef('treble').addTimeSignature('C');
// Connect it to the rendering context and draw!
stave.setContext(context).draw();
</script>
最初に、div に 出力するタグを取得する。ここでは <div id="output"></div> のタグ全体要素を取得している。次にnew Renderer(div, Renderer.Backends.SVG); でSVGでの描画処理をしてくれるようになります。他にもRenderer.Backends.CANVASという指定もできます。自分はSVGバージョンが好みです。なんか問題報告が少ない。CANVASはスケールサイズ表示がおかしいとか言ってる人もいます。renderer.resize(500, 200); は描画エリアのサイズを指定しています。そして、この描画エリアに五線譜のオブジェクトとして構築した stave を配置します。
New Stave(10, 40, 400)で、描画エリアの x = 10, y = 40の位置に幅 400 の五線譜を描きます。(410, 100)の頂点位置をみればわかりますが、きっちり 10+400 の大きさになっています。五線譜の真ん中の第三線が中央になっており縦 200 が 1 行にちょうど良いようです。楽譜部分の表示スケールを変更して小さくする予定なので、その時にまた、描画領域について考えてみたいと思います。
stave.addClef('treble').addTimeSignature('4/4'); で treble ト音記号 4/4 つまり4分の4拍子という五線譜になります。
自分は、持っているPCが12型で1920x1280を使っていますので、表示倍率を150%にしています。その関係で、実質は横サイズが1920の66%で、横は1280pxと変わらない表示領域です。したがって、Wikiの横幅も最大にして、1000pxくらいがちょうどいい限界の大きさです。しかし、そのサイズでは、4小節くらいを1行にするには楽譜が大きすぎるので、もうちょっと小さくしないといけないなとは思っていますが、その処理も、もうちょっと後でやるとします。続けて、実際の音符を配置してみます。
拍子記号は以下のようなものが使えます。
シーケンス | 意味 |
---|---|
C | Cを表示(4/4拍子) |
C| | Cの横中央に縦棒を表示(2/2拍子) |
n1/n2 | n2 分の n1 拍子 |
五線譜に音符を配置
目安になる線を引きました。左上の角の頂点が(x, y)対応で(1, 1)、そこから時計回りに(499, 1)→(499, 199)→(360, 100)です。
スクリプトは以下のようなものです。
const {
Renderer,
Stave,
StaveNote,
Voice,
Formatter
} = Vex.Flow;
const div2 = document.getElementById('output2');
const renderer2 = new Renderer(div2, Renderer.Backends.SVG);
// Configure the rendering context.
renderer2.resize(500, 200);
const context2 = renderer2.getContext();
// Create a stave of width 400 at position 10, 40 on the canvas.
const stave2 = new Stave(10, 40, 400);
// Add a clef and time signature.
stave2.addClef('treble').addTimeSignature('C|');
// Connect it to the rendering context and draw!
stave2.setContext(context2).draw();
// Create the notes
const notes2 = [
new StaveNote({
keys: ['c/5'],
duration: 'q'
}),
new StaveNote({
keys: ['d/4'],
duration: 'q'
}),
new StaveNote({
keys: ['b/4'],
duration: 'qr'
}),
new StaveNote({
keys: ['c/4', 'e/4', 'g/4'],
duration: 'q'
}),
];
// Create a voice in 4/4 and add above notes
const voice2 = new Voice({ num_beats: 4, beat_value: 4 });
voice2.addTickables(notes2);
// Format and justify the notes to 400 pixels.
new Formatter().joinVoices([voice2]).format([voice2], 350);
// Render voice
voice2.draw(context2, stave2);
変数名が最初の楽譜とダブるところは数字を付けました。一番最初の StaveNote, Voice, Formatterの部分は最初のと共通なので書き換えが必要です。あとの部分で2重で定義してもダメです。最初のがエラーになって、致命的エラーで何も描画処理がされなくなります。コード全体としては、音符を付ける部分は、五線譜に付け足すので、すべて、最初のコードより後ろに付け足す感じです。const notes2 以降が付け足した部分です。ここまで書いて言えるのは VexFlow はプログラミングに近いです。音楽や楽譜しか知らない人が、この方式で楽譜を描くのは難しいと感じると思います。ABC記述方式のモノが好まれる理由が少しわかります。ただVexflowの方が複雑な記述であるゆえに、複雑な楽譜が書ける言えるのかもしれない。まるで配列の中にひとつづつの音符を生成している感じで、ひとつの音符ごとに区切って new StaveNote() 関数に引数としてオブジェクト値(自分はこれをPerlの連想配列と似ているので連想配列とちょくちょく呼んでいます)を指定しています。プロパティ名にkeys: とduration: が指定されます。プロパティ名であることがわかりやすいように:までつけていますが名前自体は keys と duration です。keys プロパティ名に対応するものとして、配列が指定されるようです。値が一つしかなくてもです。そこに音の高さ相当する値、音階名と段階番号を付けます。実音記号のA(ラ), B(シ), C(ド), D(レ), E(ミ), F(ファ), G(ソ)に対応していて、/ で区切って段階の番号を付与します。
ピアノと同じで88音の下から1, 2, … , 10, 11 まであります。ピアノ譜のト音記号の五線譜に下に一つ補助線を付けたドが C/4 に相当します。通常、ト音記号の五線譜には E/3 が最低音、 最高の音しても G/6 までくらいを使います。C/6 から 上の音での演奏が続くときは オクターブ記号を使って、その下に描くことがあります。ギターも同じです。ギターは4オクターブしかありません。が一番上の音付近を使うときは、オクターブ記号を置いて、一段階下げて表記します。どの楽器もピアノの88鍵盤には負けます。ギターの一番低い音でもE/3です。クラリネットの音域もギターに似ていますが、最高音はギターよりも狭い範囲が一般的です。最高音のあたりはかなりうまい人だと出せなくはない音ですが狙うのはスゴイ難しいです。ベースとピアノの最低音あたりはヘ音記号の五線譜に書かれることが普通です。ピアノはト音記号とヘ音記号の二つ同時に使うこともあります。音域やばいっす。バイオリンあたりはテノール記号(ハ音記号)というト音記号でもヘ音記号でもないものを使う場合があります。この3種類しかないと思います。あとは変則の五線譜として、通常の位置とは異なる部分に、記号を配置する手法があります。
durationの値には文字列が設定されて、 q が四分音符、h が2分音符、w が全音符を表します。8, 16, 32, 64 がそれぞれ8分音符、 16分音符、 32分音符、64分音符に対応しますが、連符もありますので、ひとことで言えるほど簡単ではないです。後ろに r をつけると休符になります。rest の r だそうです。 voice2.draw(context2, stave2);
このようにして作った notes2 を Voiceという単位に格納するため new Voice({ num_beats: 4, beat_value: 4 }); でプロパティ名 num_beats と beat_value を持ったオブジェクトを引数にしています。ともに 4 拍子の中に 4つの拍子をもった音だと定義されていて、その中に、notes2 を取り込むべく、voice2.addTickables(notes2); としています。
そして、これらを400pxの五線譜の中に納まるように、new Formatter().joinVoices([voice2]).format([voice2], 350); としています。x = 350のところに最後の音符が収まっていることも確認できます。このように処理した voice2 を context2 の描画領域に stave2 の 五線譜に描画します。
duration に設定できる値は以下のようなものです。
シーケンス | 意味 |
---|---|
w | 全音符 |
h | 2分音符 |
q | 4分音符 |
8 | 8分音符 |
16 | 16分音符 |
32 | 32分音符 |
五線譜に音符を配置 2Voices
スクリプトは以下のようなものです。
<div id="output3"></div>
//******************ここから複数の楽譜表示における共通のスクリプト**
<script src="https://cdn.jsdelivr.net/npm/vexflow@4.1.0/build/cjs/vexflow.js"></script>
<script>
const {
Renderer,
Stave,
StaveNote,
Voice,
Formatter
} = Vex.Flow;
//******************ここまで共通**
// Create an SVG renderer and attach it to the DIV element named "boo".
const div3 = document.getElementById('output3');
const renderer3 = new Renderer(div3, Renderer.Backends.SVG);
// Configure the rendering context.
renderer3.resize(500, 200);
const context3 = renderer3.getContext();
// Create a stave of width 400 at position 10, 40 on the canvas.
const stave3 = new Stave(10, 40, 400);
// Add a clef and time signature.
stave3.addClef('treble').addTimeSignature('4/4');
// Connect it to the rendering context and draw!
stave3.setContext(context3).draw();
// Create the notes
const notes3 = [
new StaveNote({
keys: ['c/5'],
duration: 'q'
}),
new StaveNote({
keys: ['d/4'],
duration: 'q'
}),
new StaveNote({
keys: ['b/4'],
duration: 'qr'
}),
new StaveNote({
keys: ['c/4', 'e/4', 'g/4'],
duration: 'q'
}),
];
const notes32 = [new StaveNote({
keys: ['c/4'],
duration: 'w'
})];
// Create a voice in 4/4 and add above notes
const voices3 = [
new Voice({
num_beats: 4,
beat_value: 4
}).addTickables(notes3),
new Voice({
num_beats: 4,
beat_value: 4
}).addTickables(notes32),
];
// Format and justify the notes to 400 pixels.
new Formatter().joinVoices(voices3).format(voices3, 350);
// Render voices.
voices3.forEach(function(v) {
v.draw(context3, stave3);
});
和音を除いて、通常、一人では演奏できないような記譜法を2Voicesと呼んでいます。和音を引けない楽器ならそれだけで2Voices 3Voicesとも言えますが、この例のように1小節の中を休符と音符とで分かち合うのを1Voiceとした考え方を保有する記譜の場合は、特別な記述が必要です。別の音という意味付けになります。スコア譜なんかではよく2Voiceや3Voiceの記述が入ります。アレンジするうえで、音を足していくことはよくあるためです。ただしスコア譜では、一小節を短くして |1Voice(2Voice)|のように記譜している方式もよくあります。パートを増やせばいいじゃんと思いますが、限られたスペースにちょっとした音のためだけにパートを増やすことは通常は、実施しません。
const notes32の部分の定義のように、音符の定義を別で実施します。Voiceを生成するときに、2つの音符をまとめるときに配列に個別のオブジェクトとして定義します。そして作られた配列2の大きさのVoiceをFormatter関数を呼び出して、そのメンバ関数 joinVoices で結合します。joinVoices 関数は返り値が formatter 形式で .format も Formatter のメンバ関数です。そして 350px の中に描画するようになります。そして、2要素の配列の Voices それぞれを繰り返し処理によって、引数の描画エリア context3 五線譜 stave3 に音符を配置して描画します。
五線譜に音符を配置 付点臨時記号
スクリプトは以下のようなものです。
<div id="output4"></div>
//******************ここから複数の楽譜表示における共通のスクリプト**
<script src="https://cdn.jsdelivr.net/npm/vexflow@4.1.0/build/cjs/vexflow.js"></script>
<script>
const {
Renderer,
Stave,
StaveNote,
Voice,
Accidental,
Formatter,
Dot
} = Vex.Flow;
//******************ここまで共通**
</script>
<script>
// Create an SVG renderer and attach it to the DIV element named "boo".
const div4 = document.getElementById('output4');
const renderer4 = new Renderer(div4, Renderer.Backends.SVG);
// Configure the rendering context.
renderer4.resize(500, 200);
const context4 = renderer4.getContext();
// Create a stave of width 400 at position 10, 40 on the canvas.
const stave4 = new Stave(10, 40, 400);
// Add a clef and time signature.
stave4.addClef('treble').addTimeSignature('4/4');
// Connect it to the rendering context and draw!
stave4.setContext(context4).draw();
// Create the notes
const notes4 = [
dotted(new StaveNote({
keys: ['c##/5'],
duration: '8d'
}).addModifier(new Accidental('##'))),
new StaveNote({
keys: ['db/4'],
duration: '16'
}).addModifier(new Accidental('b')),
dotted(new StaveNote({
keys: ['b/4', 'd/5', 'f/5'],
duration: 'h'
}).addModifier(new Accidental('#'),0),[0, 2]),
dotted(new StaveNote({
keys: ['c/4', 'e/4', 'g/4'],
duration: '8'
})),
];
Formatter.FormatAndDraw(context4, stave4, notes4);
function dotted(staveNote, noteIndex = -1) {
if (noteIndex < 0) {
Dot.buildAndAttach([staveNote], {
all: true
});
} else {
for(noteIndexValue of noteIndex){
Dot.buildAndAttach([staveNote], {
index: noteIndexValue
});
}
}
return staveNote;
}
</script>
付点を付けるにはDot.buildAndAttach([ staveNoteオブジェクト ], { index: 数値 }) といった関数を実行する必要があります。例では何度も呼び出すのが面倒なので、ユーザ関数を定義しています。公式では、特定のひとつの音符にしか、付点をつけることができないユーザ関数になっていたので、引数に数値単独の値を付与するところに、数値配列に、何番目の音符に付点をつけるかを指定するようなものに変更しています。Formatter.FormatAndDraw(context4, stave4, notes4); だけでも五線譜に音符を描画できるという便利な一面も紹介してくれています。
臨時記号をつける処理をしているが、keys: ['c##/5']のように指定しても描画には反映されない。描画に影響しているのは、.addModifier(new Accidental('#'), 0) という部分です。一つ目の引数で Accidental オブジェクトのコンストラクタ引数に臨時記号の種類を与えています。##が二重シャープ。#がシャープ。bがフラット。bbが二重フラットです。今回は使いませんでしたが、ナチュラルはnです。.addModifier の2つめの引数はコードになっているときの、何番目の音に適用するかを指定しています。
改めてまとめると、臨時記号の種類は以下のようになっています。
シーケンス | 意味 |
---|---|
n | ナチュラル |
# | シャープ(半音上げる) |
## | ダブルシャープ(半音2回上げる) |
b | フラット(半音下げる) |
bb | ダブルフラット(半音2回下げる) |
Dot.…によって付点をつけるだけで、長さに不整合が起こっても記譜に問題は起こりません。どちらかというと一小節の中に納まるように配置されるかは、durationの組み合わせのほうが重要です。
今回はト音記号 treble からヘ音記号 bass に変えました。他にも以下のような記号があります。
シーケンス | 意味 |
---|---|
treble | ト音記号 𝄞 |
bass | ヘ音記号 𝄢 |
alto | ハ音記号(アルト) 𝄡 |
percussion | 打楽器記号 |
tenor | ハ音記号(テナー) 𝄡一つ上にシフト |
soprano | ハ音記号(ソプラノ) 𝄡二つ下にシフト |
mezzo-soprano | ハ音記号(メゾソプラノ) 𝄡一つ下にシフト |
baritone-c | ハ音記号(バリトン) 𝄡二つ上にシフト |
baritone-f | ヘ音記号(バリトン) 𝄢一つ下にシフト |
subbass | ヘ音記号(サブバス) 𝄢一つ上にシフト |
french | ト音記号(フレンチ) 𝄞一つ下にシフト |
五線譜に音符を配置 連桁(れんこう)・ビーム・梁 new Beam()による方法
スクリプトは以下のようなものです。
<div id="output5"></div>
//******************ここから複数の楽譜表示における共通のスクリプト**
<script src="https://cdn.jsdelivr.net/npm/vexflow@4.1.0/build/cjs/vexflow.js"></script>
<script>
const {
Renderer,
Stave,
StaveNote,
Voice,
Accidental,
Beam,
Formatter,
Dot
} = Vex.Flow;
//******************ここまで共通**
</script>
<script>
// Create an SVG renderer and attach it to the DIV element named "boo".
const div5 = document.getElementById('output5');
const renderer5 = new Renderer(div5, Renderer.Backends.SVG);
// Configure the rendering context.
renderer5.resize(500, 200);
const context5 = renderer5.getContext();
// Create a stave of width 400 at position 10, 40 on the canvas.
const stave5 = new Stave(10, 40, 400);
// Add a clef and time signature.
stave5.addClef('treble').addTimeSignature('4/4');
// Connect it to the rendering context and draw!
stave5.setContext(context5).draw();
// Create the notes
const notes51 = [
dotted(
new StaveNote({
keys: ["e##/5"],
duration: "8d",
}).addModifier(new Accidental("##"))
),
new StaveNote({
keys: ["b/4"],
duration: "16",
}).addModifier(new Accidental("b")),
];
const notes52 = [
new StaveNote({
keys: ["c/4"],
duration: "8",
}),
new StaveNote({
keys: ["d/4"],
duration: "16",
}),
new StaveNote({
keys: ["e/4"],
duration: "16",
}).addModifier(new Accidental("b")),
];
const notes53 = [
new StaveNote({
keys: ["d/4"],
duration: "16",
}),
new StaveNote({
keys: ["e/4"],
duration: "16",
}).addModifier(new Accidental("#")),
new StaveNote({
keys: ["g/4"],
duration: "32",
}),
new StaveNote({
keys: ["a/4"],
duration: "32",
}),
new StaveNote({
keys: ["g/4"],
duration: "16",
}),
];
const notes54 = [
new StaveNote({
keys: ["d/4"],
duration: "q",
}),
];
const allNotes5 = notes51.concat(notes52).concat(notes53).concat(notes54);
// Create the beams for the first three groups.
// This hides the normal stems and flags.
const beams5 = [new Beam(notes51), new Beam(notes52), new Beam(notes53)];
Formatter.FormatAndDraw(context5, stave5, allNotes5);
// Draw the beams and stems.
beams5.forEach((b) => {
b.setContext(context5).draw();
});
//********前の記譜と共通の関数です。2重定義しないように注意******
function dotted(staveNote, noteIndex = -1) {
if (noteIndex < 0) {
Dot.buildAndAttach([staveNote], {
all: true
});
} else {
for(noteIndexValue of noteIndex){
Dot.buildAndAttach([staveNote], {
index: noteIndexValue
});
}
}
return staveNote;
}
//********ここまで
</script>
音符の旗を描画せずにつなぐのを連桁といいます。表紙のひと固まりを繋ぎ演奏しやすいような表記にしています。8分音符の場合4つをつないで、2分音符のかたまりにしたりもします。例の楽譜では3拍目が16分音符で四分音符1つ分のひとかたまりになっています。16分音符の3つ目が32分なっています。キッチリ演奏しようとすると32分はめちゃくちゃ短いのでアタックからの立ち上がりが早い楽器でかつ技術力が要求される奏法になってきます。そんな演奏面の話はどうでもよいでしょうか。
連桁は、Beam というシーケンスによって描画されます。Notesを連桁にしたい塊でひとつになるように作ると、const beams5 = [new Beam(notes51), new Beam(notes52), new Beam(notes53)];という記法によって、連桁のかたまりごとに配列にすることができます。このそれぞれの要素について関数、new Beam(notes51).setContext(context5).draw();のような操作によって蓮桁の描画準備がされます。小節の全体は const allNotes5 = notes51.concat(notes52).concat(notes53).concat(notes54); のように結合されたものを.FormatAndDraw(context5, stave5, allNotes5);のようにして描画されます。
五線譜に音符を配置 連桁(れんこう)・ビーム・梁 Beam.generateBeamsによる方法
スクリプトは以下のようなものです。
<div id="output6"></div>
//******************ここから複数の楽譜表示における共通のスクリプト**
<script src="https://cdn.jsdelivr.net/npm/vexflow@4.1.0/build/cjs/vexflow.js"></script>
<script>
const {
Renderer,
Stave,
StaveNote,
Voice,
Accidental,
Beam,
Formatter,
Dot
} = Vex.Flow;
//******************ここまで共通**
</script>
<script>
const { Renderer, Stave, Accidental, StaveNote, Beam, Formatter, Dot } = Vex.Flow;
// Create an SVG renderer and attach it to the DIV element named "boo".
const div6 = document.getElementById("output6");
const renderer6 = new Renderer(div6, Renderer.Backends.SVG);
// Configure the rendering context.
renderer6.resize(500, 500);
const context6 = renderer6.getContext();
// Create a stave of width 400 at position 10, 40 on the canvas.
const stave6 = new Stave(10, 40, 400);
// Add a clef and time signature.
stave6.addClef("treble").addTimeSignature("4/4");
// Connect it to the rendering context and draw!
stave6.setContext(context6).draw();
const notes6 = [
dotted(new StaveNote({ keys: ["e##/5"], duration: "8d" }).addModifier(new Accidental("##"))),
new StaveNote({ keys: ["b/4"], duration: "16" }).addModifier(new Accidental("b")),
new StaveNote({ keys: ["c/4"], duration: "8" }),
new StaveNote({ keys: ["d/4"], duration: "16" }),
new StaveNote({ keys: ["e/4"], duration: "16" }).addModifier(new Accidental("b")),
new StaveNote({ keys: ["d/4"], duration: "16" }),
new StaveNote({ keys: ["e/4"], duration: "16" }).addModifier(new Accidental("#")),
new StaveNote({ keys: ["g/4"], duration: "32" }),
new StaveNote({ keys: ["a/4"], duration: "32" }),
new StaveNote({ keys: ["g/4"], duration: "16" }),
new StaveNote({ keys: ["d/4"], duration: "q" }),
];
const beams6 = Beam.generateBeams(notes6);
Formatter.FormatAndDraw(context6, stave6, notes6);
beams6.forEach((b) => {
b.setContext(context).draw();
});
//********前の記譜と共通の関数です。2重定義しないように注意******
function dotted(staveNote, noteIndex = -1) {
if (noteIndex < 0) {
Dot.buildAndAttach([staveNote], {
all: true
});
} else {
for(noteIndexValue of noteIndex){
Dot.buildAndAttach([staveNote], {
index: noteIndexValue
});
}
}
return staveNote;
}
//********ここまで
</script>
VexFlow 使い方へ戻る。
<yjavascript></script> <script> const { Renderer, Stave, Accidental, StaveNote, Beam, Formatter, Dot } = Vex.Flow;
// Create an SVG renderer and attach it to the DIV element named "boo". const div6 = document.getElementById("output6"); const renderer6 = new Renderer(div6, Renderer.Backends.SVG);
// Configure the rendering context. renderer6.resize(500, 500); const context6 = renderer6.getContext();
// Create a stave of width 400 at position 10, 40 on the canvas. const stave6 = new Stave(10, 40, 400);
// Add a clef and time signature. stave6.addClef("treble").addTimeSignature("4/4");
// Connect it to the rendering context and draw! stave6.setContext(context6).draw();
const notes6 = [
dotted(new StaveNote({ keys: ["e##/5"], duration: "8d" }).addModifier(new Accidental("##"))), new StaveNote({ keys: ["b/4"], duration: "16" }).addModifier(new Accidental("b")), new StaveNote({ keys: ["c/4"], duration: "8" }), new StaveNote({ keys: ["d/4"], duration: "16" }), new StaveNote({ keys: ["e/4"], duration: "16" }).addModifier(new Accidental("b")), new StaveNote({ keys: ["d/4"], duration: "16" }), new StaveNote({ keys: ["e/4"], duration: "16" }).addModifier(new Accidental("#")), new StaveNote({ keys: ["g/4"], duration: "32" }), new StaveNote({ keys: ["a/4"], duration: "32" }), new StaveNote({ keys: ["g/4"], duration: "16" }), new StaveNote({ keys: ["d/4"], duration: "q" }),
];
const beams6 = Beam.generateBeams(notes6); Formatter.FormatAndDraw(context6, stave6, notes6); beams6.forEach((b) => {
b.setContext(context).draw();
}); </script>