VexFlow 音符

提供:yonewiki

VexFlow 使い方に戻る。

概要

 音符を記述するまでにその土台となる楽譜の構造を知らなければならなかったので、遅くなりましたが、ようやっと本文をえがけるようになるという感じです。ただし、音符も構成要素は複雑なので、ここではシンプルな音符の機能のみを紹介したいと思います。旗同士を繋ぐ連桁(れんこう:英 Beem ビーム)、連符や休符については別の項目で記します。この先の3つあたりまで把握できてやっと音を記録する楽譜というものが構成できるようになります。あと3つ先までは音符の範疇。ここでは、その一部しか紹介しないことに注意しておいて下さい。

 

音符の種類

 小節において何拍分を鳴らすかの違いで音符の種類が異なります。まずは、種類について見てみましょう。



<script src="https://wiki.yo-net.jp/vexflow/build/cjs/vexflow.js"></script>
<div id="yonet202302_Output01"></div>
<script>
(function(){
  const {
    Factory,
    Stave,
    StaveNote,
    Formatter,
    Voice,
    Barline,
  } = Vex.Flow;

  const f = new Factory({ renderer: { elementId: 'yonet202302_Output01', width: 1500, height: 195 } });
  const ctx = f.getContext();
  const stave1 = new Stave(10, 50, 350).setContext(ctx).draw();
  stave1.addClef("treble").setContext(ctx).draw();
  stave1.addTimeSignature('C').setContext(ctx).draw();

  const notes1 = [
    new StaveNote({ keys: ["c/4"], duration: "1" }), 
  ];
  Formatter.FormatAndDraw(ctx, stave1, notes1);

  const stave2 = new Stave(360, 50, 350).setContext(ctx).draw();
  const notes2 = [
    new StaveNote({ keys: ["c/4"], duration: "2" }), 
    new StaveNote({ keys: ["d/4"], duration: "4" }), 
    new StaveNote({ keys: ["e/4"], duration: "4" }), 
  ];
  Formatter.FormatAndDraw(ctx, stave2, notes2);

  const stave3 = new Stave(710, 50, 450).setEndBarType(Barline.type.END).setContext(ctx).draw();
  const notes3 = [
    new StaveNote({ keys: ["c/4"], duration: "8" }), 
    new StaveNote({ keys: ["d/4"], duration: "8" }), 
    new StaveNote({ keys: ["e/4"], duration: "16" }), 
    new StaveNote({ keys: ["f/4"], duration: "16" }), 
    new StaveNote({ keys: ["g/4"], duration: "16" }), 
    new StaveNote({ keys: ["a/4"], duration: "16" }), 
    new StaveNote({ keys: ["c/4"], duration: "8" }), 
    new StaveNote({ keys: ["d/4"], duration: "8" }), 
    new StaveNote({ keys: ["e/4"], duration: "16" }), 
    new StaveNote({ keys: ["f/4"], duration: "16" }), 
    new StaveNote({ keys: ["g/4"], duration: "16" }), 
    new StaveNote({ keys: ["a/4"], duration: "16" }), 
  ];
  notes3.forEach((StaveNote) => StaveNote.setStave(stave3));
  const voice3 = new Voice({beat_value: 4, num_beats: 4}).setMode(3).addTickables(notes3);
  new Formatter().joinVoices([voice3]).formatToStave([voice3], stave3);
  stave3.setContext(ctx).draw();
  voice3.setContext(ctx).draw();

})();
</script>

 行19~21までが一小節目の音符について記述しているところです。自分で作った定数変数note1に一小節分の音符情報を配列にして記述します。StaveNoteオブジェクトをコンストラクタに引数を渡してものが一つの音符となるようにして、づづけて記載していきますが、ここでは全音符を指定したので、一小節にはこの情報だけになります。これ以上の配列追加をすると、一小節の情報に矛盾が発生し始めます。このように、一小節にわたされる要素数と一小節を構築する音の長さの総和が拍子記号で設定された値と矛盾していないかについてはすべて、作成者が管理しなければなりません。


 StaveNoteオブジェクトの引数には以下のような要素を持つオブジェクトが指定できます。

  • keys
 音の高さを指定しますト音記号の五線譜の下に追加した線の一つ目が C/4 という音になり、Do ド にあたります。順番にC/4, D/4, E/4, F/4, G/4, A/4, B/4, C/5, D/5, E/5… というように音が高くなっていきます。ヘ音記号の場合は、通常、第5線の上に追加した線の上、高音のラにあたるところが、C/4になります。音部記号まで反映した値にするには、オブジェクトの要素に clef を与えなければなりません。詳細は後に記述します。小節に描く音がどんな音部記号に従っているかは、連動していません。この値の中で閉じた世界でどのように指定するかをすべての情報を記載することで、振る舞いが変わります。 配列にして渡す必要があります。配列要素が2個以上になるものは和音として描画されます。音の名前の後ろに/x1, /x2, /x3 とつけるとそれぞれのパターンの × マークが丸の代わりに描かれます。Mute音やパーカッション音に利用できます。
  • duration
 音の長さを指定します。1 = 全音符(w)、2 = 2分音符(h)、4 = 4分音符(q)、 8 = 8分音符、16 = 16分音符と対応しています。
  • clef
 音部記号を指定できます。keysに指定する音の高さがどの音部記号に従うかを設定するモノです。
  • stem_direction
 音符に旗が付く場合、1で上向き -1で下向きにできます。

和音

 さきほど単音の音符を描きました。和音を記述してみます。



<div id="yonet202302_Output02"></div>
<script>
(function(){
  const {
    Factory,
    Stave,
    StaveNote,
    Formatter,
    Voice,
    Barline,
  } = Vex.Flow;

  const f = new Factory({ renderer: { elementId: 'yonet202302_Output02', width: 1500, height: 195 } });
  const ctx = f.getContext();
  const stave1 = new Stave(10, 50, 350).setContext(ctx).draw();
  stave1.addClef("treble").setContext(ctx).draw();
  stave1.addTimeSignature('C').setContext(ctx).draw();

  const notes1 = [
    new StaveNote({ keys: ["c/4", "e/4", "g/4"], duration: "1" }), 
  ];
  Formatter.FormatAndDraw(ctx, stave1, notes1);

  const stave2 = new Stave(360, 50, 350).setContext(ctx).draw();
  const notes2 = [
    new StaveNote({ keys: ["c/4", "e/4", "g/4"], duration: "2" }), 
    new StaveNote({ keys: ["d/4", "f/4", "a/4"], duration: "4" }), 
    new StaveNote({ keys: ["e/4", "g/4", "b/4"], duration: "4" }), 
  ];
  Formatter.FormatAndDraw(ctx, stave2, notes2);

  const stave3 = new Stave(710, 50, 450).setEndBarType(Barline.type.DOUBLE).setContext(ctx).draw();
  const notes3 = [
    new StaveNote({ keys: ["c/4"], duration: "8" }), 
    new StaveNote({ keys: ["d/4"], duration: "8" }), 
    new StaveNote({ keys: ["e/4"], duration: "16" }), 
    new StaveNote({ keys: ["f/4"], duration: "16" }), 
    new StaveNote({ keys: ["g/4"], duration: "16" }), 
    new StaveNote({ keys: ["a/4"], duration: "16" }), 
    new StaveNote({ keys: ["c/4"], duration: "8" }), 
    new StaveNote({ keys: ["d/4"], duration: "8" }), 
    new StaveNote({ keys: ["e/4"], duration: "16" }), 
    new StaveNote({ keys: ["f/4"], duration: "16" }), 
    new StaveNote({ keys: ["g/4"], duration: "16" }), 
    new StaveNote({ keys: ["a/4"], duration: "16" }), 
  ];
  notes3.forEach((StaveNote) => StaveNote.setStave(stave3));
  const voice3 = new Voice({beat_value: 4, num_beats: 4}).setMode(3).addTickables(notes3);
  new Formatter().joinVoices([voice3]).formatToStave([voice3], stave3);
  stave3.setContext(ctx).draw();
  voice3.setContext(ctx).draw();

})();
</script>

 20行目のように、keysに指定する配列を2要素以上にすると、和音になります。和音の中に異なる長さの音が含まれるようにするにはVoiceという考え方を導入する必要があります。ひとつの楽譜の中に2つ以上のパートを記載するときもVoiceという考え方が使われます。もっと発展的には、2つめのパートをカッコをつけて|1つめのパート(2つめのパート)|という具合に表記するパターンもありますがVexFlowではそのような表記には対応していません。


付点音符

 よくつかわれる音符に、音符の長さの半分を付け足す、付点音符というものがあります。付点音符を描くには以下のようにします。


<div id="yonet202302_Output03"></div>
<script>
(function(){
  const {
    Factory,
    Stave,
    StaveNote,
    Formatter,
    Voice,
    Barline,
    Dot,
  } = Vex.Flow;

  const f = new Factory({ renderer: { elementId: 'yonet202302_Output03', width: 1500, height: 195 } });
  const ctx = f.getContext();
  const stave1 = new Stave(10, 50, 350).setContext(ctx).draw();
  stave1.addClef("treble").setContext(ctx).draw();
  stave1.addTimeSignature('C').setContext(ctx).draw();

  const notes1 = [
    new StaveNote({ keys: ["c/4", "e/4", "g/4"], duration: "1" }), 
  ];
  Formatter.FormatAndDraw(ctx, stave1, notes1);

  const stave2 = new Stave(360, 50, 350).setContext(ctx).draw();
  const notes2 = [
    new StaveNote({ keys: ["c/4", "e/4", "g/4"], duration: "2" }), 
    new StaveNote({ keys: ["d/4", "f/4", "a/4"], duration: "4" }), 
    new StaveNote({ keys: ["e/4", "g/4", "b/4"], duration: "4" }), 
  ];
  Formatter.FormatAndDraw(ctx, stave2, notes2);

  const stave3 = new Stave(710, 50, 450).setEndBarType(Barline.type.DOUBLE).setContext(ctx).draw();
  const notes3 = [
    dotted(new StaveNote({ keys: ["c/4"], duration: "8d" })), 
    new StaveNote({ keys: ["d/4"], duration: "16" }), 
    dotted(new StaveNote({ keys: ["e/4"], duration: "16d" })), 
    new StaveNote({ keys: ["f/4"], duration: "32" }), 
    dotted(new StaveNote({ keys: ["g/4"], duration: "16d" })), 
    new StaveNote({ keys: ["a/4"], duration: "32" }), 
    dotted(new StaveNote({ keys: ["c/4"], duration: "8d" })), 
    new StaveNote({ keys: ["d/4"], duration: "16" }), 
    dotted(new StaveNote({ keys: ["e/4"], duration: "16d" })), 
    new StaveNote({ keys: ["f/4"], duration: "32" }), 
    dotted(new StaveNote({ keys: ["g/4"], duration: "16d" })), 
    new StaveNote({ keys: ["a/4"], duration: "32" }), 
  ];
  notes3.forEach((StaveNote) => StaveNote.setStave(stave3));
  const voice3 = new Voice({beat_value: 4, num_beats: 4}).setMode(3).addTickables(notes3);
  new Formatter().joinVoices([voice3]).formatToStave([voice3], stave3);
  stave3.setContext(ctx).draw();
  voice3.setContext(ctx).draw();

  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([new StaveNote({ keys: ["c/4"], duration: "8d" }), {all: true});のようにする必要があります。durationには末尾にdを付けます。このdは付点をつけるという部分には影響が発生しませんが、Voiceという概念には影響があって、音符の間隔の描画に変化が発生します。直接見た目が変わるわけではないので、 d を付け忘れがちになるかもしれませんが、d をつけましょう。非常に長い関数表記になるので、JavaScriptの関数機能を使って、簡単に定義できるように工夫しています。Dot.buildAndAttach関数では、第一引数で指定した引数の音符に対して、第二引数では、何番目の音程に付点をつけるかという設定ができるようになっています。{all:true}というオブジェクトを与える場合と、{index: 0}のように0番目の和音構成要素に対して付点をつけるというような処理にすることもできるようになっています。和音全てのindexの番号を指定していく必要があり、何度もDot.buildAndAttach関数を呼び出す必要があります。自作関数を使わない場合の手数の多さに気づかされると思います。


 したがって、自作関数dottedも、dotted(new StaveNote({ keys: ["c/4", "e/4", "g/4"], duration: "2" }),[0,1,2])のように第二引数を指定することができるようになっています。

 

臨時記号付き音符

 さきほど単音の音符を描きました。臨時記号を記述してみます。これができないと音符のうちの5つが鳴らせないことになってしまう基本中の基本の音符の操作なので、音符の項目で臨時記号を説明したいと思います。



<div id="yonet202302_Output04"></div>
<script>
(function(){
  const {
    Factory,
    Stave,
    StaveNote,
    Formatter,
    Voice,
    Barline,
    Accidental,
  } = Vex.Flow;

  const f = new Factory({ renderer: { elementId: 'yonet202302_Output04', width: 1500, height: 195 } });
  const ctx = f.getContext();
  const stave1 = new Stave(10, 50, 350).setContext(ctx).draw();
  stave1.addClef("treble").setContext(ctx).draw();
  stave1.addTimeSignature('C').setContext(ctx).draw();

  const notes1 = [
    new StaveNote({ keys: ["c/4", "e/4b", "g/4"], duration: "1" })
    .addModifier(new Accidental('b'), 1), 
  ];
  Formatter.FormatAndDraw(ctx, stave1, notes1);

  const stave2 = new Stave(360, 50, 350).setContext(ctx).draw();
  const notes2 = [
    new StaveNote({ keys: ["c/4", "e/4", "g/4"], duration: "2" }) 
    .addModifier(new Accidental('n'), 1), 
    new StaveNote({ keys: ["d/4", "f/4", "a/4#"], duration: "4" })
    .addModifier(new Accidental('#'), 2), 
    new StaveNote({ keys: ["e/4", "g/4", "b/4##"], duration: "4" })
    .addModifier(new Accidental('##'), 2), 
  ];
  Formatter.FormatAndDraw(ctx, stave2, notes2);

  const stave3 = new Stave(710, 50, 450).setEndBarType(Barline.type.DOUBLE).setContext(ctx).draw();
  const notes3 = [
    new StaveNote({ keys: ["c/4"], duration: "8" }), 
    new StaveNote({ keys: ["d/4"], duration: "8" }), 
    new StaveNote({ keys: ["e/4"], duration: "16" }), 
    new StaveNote({ keys: ["f/4"], duration: "16" }), 
    new StaveNote({ keys: ["g/4"], duration: "16" }), 
    new StaveNote({ keys: ["a/4"], duration: "16" }), 
    new StaveNote({ keys: ["c/4"], duration: "8" }), 
    new StaveNote({ keys: ["d/4"], duration: "8" }), 
    new StaveNote({ keys: ["e/4"], duration: "16" }), 
    new StaveNote({ keys: ["f/4"], duration: "16" }), 
    new StaveNote({ keys: ["g/4"], duration: "16" }), 
    new StaveNote({ keys: ["a/4"], duration: "16" }), 
  ];
  notes3.forEach((StaveNote) => StaveNote.setStave(stave3));
  const voice3 = new Voice({beat_value: 4, num_beats: 4}).setMode(3).addTickables(notes3);
  new Formatter().joinVoices([voice3]).formatToStave([voice3], stave3);
  stave3.setContext(ctx).draw();
  voice3.setContext(ctx).draw();

})();
</script>

 Accidentalオブジェクトを利用しますので、11行目でAccidentalという要素を追加しました。22行目で実際にオブジェクトを生成しています。New StaveNote(…).addModifier(new Accidental('b'), 1) という配列要素にすることで、臨時記号のついた音符になります。addModifier関数で音符に付加する情報を指定できて、addModifier関数の第一引数にはAccidentalオブジェクトを生成したものを付与することができます。そのうちのひとつとして、Accidental関数の引数に b を与えるとフラットになります。addModifier関数の第二引数には何番目の音程に対して適用するものか指定します。


 例では以下のようなものを定義しました。


  • 11行目 b で1小節目の全音符の和音構成要素1にフラット(※構成要素番号は0から始まります。)
  • 29行目 n で2小節目の1拍目2分音符の和音構成要素1にナチュラル
  • 29行目 # で2小節目の3拍目4分音符の和音構成要素2にシャープ
  • 29行目 ## で2小節目の4拍目4分音符の和音構成要素2にダブルシャープ


 調号のところで、使った、切り替え記号種類の全てが使えます。微分音使うことあるのかなぁ。見たことないけど。

 

VexFlow 使い方に戻る。