VexFlow 入門その1

提供:yonewiki

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('4/4');

// 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行にするには楽譜が大きすぎるので、もうちょっと小さくしないといけないなとは思っていますが、その処理も、もうちょっと後でやるとします。続けて、実際の音符を配置してみます。

 

五線譜に音符を配置

 目安になる線を引きました。左上の角の頂点が(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('4/4');

// 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 の 五線譜に描画します。

 

五線譜に音符を配置 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つめの引数はコードになっているときの、何番目の音に適用するかを指定しています。


 Dot.…によって付点をつけるだけで、長さに不整合が起こっても記譜に問題は起こりません。どちらかというと一小節の中に納まるように配置されるかは、durationの組み合わせのほうが重要です。


 今回はト音記号 treble からヘ音記号 bass に変えました。他にも以下のような記号があります。

シーケンス 意味
treble ト音記号 𝄞
bass ヘ音記号 𝄢
alto ハ音記号(アルト) 𝄡
percussion 打楽器記号
tenor ハ音記号(テナー) 𝄡

 

VexFlow 使い方へ戻る。