VexFlow 連符
概要
連符を描いてみたいと思います。tupletというのが連符のキーワードになります。
3連符
<div id="output"></div>
<script>
(function(){
// This approach to importing classes works in CJS contexts (i.e., a regular <script src="..."> tag).
const { Stave, StaveNote, Beam, Formatter, Renderer, Tuplet } = Vex;
// Create an SVG renderer and attach it to the DIV element with id="output".
const div = document.getElementById("output");
const renderer = new Renderer(div, Renderer.Backends.SVG);
// Configure the rendering context.
renderer.resize(890, 130);
const context = renderer.getContext();
// Measure 1
const staveMeasure1 = new Stave(10, 0, 470);
staveMeasure1.addClef("treble").setContext(context).draw();
const notesMeasure1 = [
new StaveNote({ keys: ["c/4"], 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" })
];
// Helper function to justify and draw a 4/4 voice
Formatter.FormatAndDraw(context, staveMeasure1, notesMeasure1);
// Measure 2 - second measure is placed adjacent to first measure.
const staveMeasure2 = new Stave(staveMeasure1.width + staveMeasure1.x, 0, 400);
const notesMeasure2_part1 = [
new StaveNote({ keys: ["c/4"], duration: "8" }),
new StaveNote({ keys: ["d/4"], duration: "8" }),
new StaveNote({ keys: ["b/4"], duration: "8" }),
new StaveNote({ keys: ["c/4", "e/4", "g/4"], duration: "8" })
];
const notesMeasure2_part2 = [
new StaveNote({ keys: ["c/4"], duration: "8" }),
new StaveNote({ keys: ["c/4"], duration: "8" }),
new StaveNote({ keys: ["c/4"], duration: "8" }),
];
const notesMeasure2_part3 = [
new StaveNote({ keys: ["b/4"], duration: "8" }),
new StaveNote({ keys: ["c/4", "e/4", "g/4"], duration: "8" })
];
// Create the beams for 8th notes in second measure.
const beam1 = new Beam(notesMeasure2_part1);
const beam2 = new Beam(notesMeasure2_part2);
const tuplet2 = new Tuplet(notesMeasure2_part2);
const beam3 = new Beam(notesMeasure2_part3);
const notesMeasure2 = notesMeasure2_part1.concat(notesMeasure2_part2).concat(notesMeasure2_part3);
staveMeasure2.setContext(context).draw();
Formatter.FormatAndDraw(context, staveMeasure2, notesMeasure2);
// Render beams
beam1.setContext(context).draw();
beam2.setContext(context).draw();
tuplet2.setContext(context).draw();
beam3.setContext(context).draw();
})();
</script>
12連符(間違えている例)
描けばいいってもんじゃないらしい。連符にも楽典の法則はあって、2小節目四拍目のように12:2だと後ろの2が音符の何個分を12個に割るのかという意味なので、32分音符の2個分は16分音符に相当するものを12連にするという意味になるらしい。12:8なら正しいことになる。かといってBeamの音を8分(1本線)とか16分(2本線)にかえていいかというとそれもダメです。8連以上の連符の場合は元の音1つ分にあたる4分音符の3段階下の音で表記することに決まっています。12:2の後ろの:2が表示されていなければ、それでも良いのですが、表示されてしまう書き方になっているみたい。困ったな。パート譜を作るならもうちょっと詰めて描画するべきだろうね。スコアの体で1行2小節の描き方をしてしまった。ちなみにダフニスとクロエ第二組曲夜明けの第三バイオリンの最初の12連符を描きました。吹奏楽でやったんで、Bbクラリネットに移行したときの譜面になっています。四分音符が1分当たり50の速さで演奏します。疲れるバックサウンドだな。ラヴェルはサディストだな。いいんだよなでも。川のせせらぎのように聞こえるもんな。許す。けど、あなたのこの指示のせいで、青春の4か月を奪ったよ。このパターンを20種類くらい練習しないといけなかったな。この調子が3分以上つづく曲ってバカだろコレ。みたいなね。それで、自分は次々とパート(つまり第三バイオリンから第一バイオリンにさせられたり、やっぱ二をやれとか、いや四がいなくなったから四たのむわとかなってバスクラリネットが不足してるからやってみるかとかね)を変えさせられて、バスクラリネットもやったし、こんなの吹けるわけねぇ。ってなったのを通り越して、全部を知り尽くした気がしてた夏だったな。結局、コンクールでれなかったし。なかなか感慨深い曲だわ。良すぎるよな。この曲。DTMに打ち込んでせせらぎをMAX付近で聴くダフニスとクロエの夜明けもいいんだよな。神々の遊びだよ。難易度はディオニソスの祭りとダフニスとクロエの2強が最強説だよ。これを知ったら他のすべての曲なんてちょろいって思える。まぁ折角ちょろくても、結局大した演奏できないんだけどベースがあれだからな。下手だから。
なので、Tuplet のコンストラクタで ratioed: flase が必要ってことがこの2つ先の記事を書いてるときに発見されたので、2小節目の4拍目以外の比率表示を消しました。でも付割がまえのめりなので、やっぱりBeatを指定する方法でも連符を描けないと駄目だな。もうしばらく考えてみる。
<div id="output2"></div>
<script>
(function(){
// This approach to importing classes works in CJS contexts (i.e., a regular <script src="..."> tag).
const { Stave, StaveNote, Beam, Formatter, Renderer, Tuplet, Voice } = Vex;
// Create an SVG renderer and attach it to the DIV element with id="output".
const div = document.getElementById("output2");
const renderer = new Renderer(div, Renderer.Backends.SVG);
// Configure the rendering context.
renderer.resize(1695, 250);
const context = renderer.getContext();
// Measure 1
const staveMeasure1 = new Stave(10, 50, 880);
staveMeasure1.addClef("treble").addKeySignature('E').addTimeSignature('4/4').setContext(context).draw();
const notesMeasure1 = [
new StaveNote({ keys: ["d/5"], duration: "wr" }),
];
// Helper function to justify and draw a 4/4 voice
Formatter.FormatAndDraw(context, staveMeasure1, notesMeasure1);
// Measure 2 - second measure is placed adjacent to first measure.
const staveMeasure2 = new Stave(staveMeasure1.width + staveMeasure1.x, 50, 800);
const notesMeasure2_part1 = [
new StaveNote({ keys: ["G#/5"], duration: "32" }),
new StaveNote({ keys: ["E/5"], duration: "32" }),
new StaveNote({ keys: ["F#/5"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["D#/5"], duration: "32" }),
new StaveNote({ keys: ["A/4"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["G#/4"], duration: "32" }),
new StaveNote({ keys: ["B/4"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["E/5"], duration: "32" }),
new StaveNote({ keys: ["F#/5"], duration: "32" }),
];
//ドA レB ミC# ファD ソE ラF# シ(G#→b→G) ドA
//ド(C→#→C#) レD ミE ファ(F→#→F#) ソG ラA シB ドC
//ド(Bb→#→B) レ(C→#→C#) ミD ファ(Eb→#→E) ソ(F→#→F#) ラG シA ドB(Bb→#→B)
const notesMeasure2_part2 = [
new StaveNote({ keys: ["G#/5"], duration: "32" }),
new StaveNote({ keys: ["E/5"], duration: "32" }),
new StaveNote({ keys: ["F#/5"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["D#/5"], duration: "32" }),
new StaveNote({ keys: ["A/4"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["G#/4"], duration: "32" }),
new StaveNote({ keys: ["B/4"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["E/5"], duration: "32" }),
new StaveNote({ keys: ["F#/5"], duration: "32" }),
];
const notesMeasure2_part3 = [
new StaveNote({ keys: ["G#/5"], duration: "32" }),
new StaveNote({ keys: ["E/5"], duration: "32" }),
new StaveNote({ keys: ["F#/5"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["D#/5"], duration: "32" }),
new StaveNote({ keys: ["A/4"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["G#/4"], duration: "32" }),
new StaveNote({ keys: ["B/4"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["E/5"], duration: "32" }),
new StaveNote({ keys: ["F#/5"], duration: "32" }),
];
const notesMeasure2_part4 = [
new StaveNote({ keys: ["G#/5"], duration: "32" }),
new StaveNote({ keys: ["E/5"], duration: "32" }),
new StaveNote({ keys: ["F#/5"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["D#/5"], duration: "32" }),
new StaveNote({ keys: ["A/4"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["G#/4"], duration: "32" }),
new StaveNote({ keys: ["B/4"], duration: "32" }),
new StaveNote({ keys: ["C#/5"], duration: "32" }),
new StaveNote({ keys: ["E/5"], duration: "32" }),
new StaveNote({ keys: ["F#/5"], duration: "32" }),
];
// Create the beams for 8th notes in second measure.
const beam1 = new Beam(notesMeasure2_part1);
const tuplet1 = new Tuplet(notesMeasure2_part1,{ratioed: false});
const beam2 = new Beam(notesMeasure2_part2);
const tuplet2 = new Tuplet(notesMeasure2_part2,{ratioed: false});
const beam3 = new Beam(notesMeasure2_part3);
const tuplet3 = new Tuplet(notesMeasure2_part3,{ratioed: false});
const beam4 = new Beam(notesMeasure2_part4);
const tuplet4 = new Tuplet(notesMeasure2_part4);
const voice = new Voice({ time: { num_beats: 1, beat_value: 4 } });
voice.setStrict(true);
voice.addTickables(notesMeasure2_part1);
//new Formatter().joinVoices([voice]).formatToStave([voice], staveMeasure2);
const notesMeasure2 = notesMeasure2_part1.concat(notesMeasure2_part2).concat(notesMeasure2_part3).concat(notesMeasure2_part4);
staveMeasure2.setContext(context).draw();
Formatter.FormatAndDraw(context, staveMeasure2, notesMeasure2);
// Render beams
beam1.setContext(context).draw();
tuplet1.setContext(context).draw();
beam2.setContext(context).draw();
tuplet2.setContext(context).draw();
beam3.setContext(context).draw();
tuplet3.setContext(context).draw();
beam4.setContext(context).draw();
tuplet4.setContext(context).draw();
})();
</script>
テスト
まぁな、わかんね。この後ろに3:2とかってなるセンスがわからん。でも意味としては8分の5の4拍で3連符だから3:2なんだろうね。8分の5の1拍で3連が3:1か。どういう意味。いや連符のこういう表記って理解してなかったわ。ちょっと勉強してこよかな。楽典か。この年になって楽典みるとはおもわんかったわ。(離席…)んー楽典を読んで来たった。3連符は、割りたい音のひとつ短い音符を使って書き、4~7までは、ふたつ短い音符を、8以降は、みっつ短い音符で16以降は4つ短い音符を使うとあります。なんだよ3:2って、書いてねぇよそんなこと。でも4分音符を3連するってことは2分音符相当を3つに割るってこと。やっぱり8分の4が2分音符相当なので、あってるわ。8分音符で3連は4分音符相当なはずだから8分の2だから8分の6になるわ。8分の5じゃねぇわ。でも、この3:2や3:1がこのルールを捻じ曲げることが出来てるってことなのかもしれない。もうちょっと楽典あさるか。(離席…)ほうほう。うしろのやつは、表記されてる音符の何個分にって意味らしい。なので最初の3連は4分音符2個分を3連で。という意味です。後ろの3連は8分音符1つ分を3連にって意味になるね。なので正しい表記ですね。ひとつ前に12連符は間違えてるってことだな。32分音符2つ分に12個って意味になるからね。というわけで、Beatをしっかりフォーマットしないと、あり得ない譜面になるってことだな。むずかしい。
(function(){
const set = (key) => (value) => (object) => {
object[key] = value;
return object;
};
const setStemDirection = set('stem_direction');
// This approach to importing classes works in CJS contexts (i.e., a regular <script src="..."> tag).
const { Factory, Stave, StaveNote, Beam, Formatter, Renderer, Tuplet, Voice, Stem } = Vex;
const setStemUp = setStemDirection(Stem.UP);
const setStemDown = setStemDirection(Stem.DOWN);
const f = new Factory({ renderer: { elementId: 'output3', width: 490, height: 200 } });
// Create an SVG renderer and attach it to the DIV element with id="output".
const stave = f.Stave({ x: 10, y: 40, width: 470 }).addTimeSignature('5/8');
const notes = [
{ keys: ['f/4'], duration: '4' },
{ keys: ['c/4'], duration: '4' },
{ keys: ['d/4'], duration: '4' },
{ keys: ['d/5'], duration: '8' },
{ keys: ['g/5'], duration: '8' },
{ keys: ['b/4'], duration: '8' },
]
.map(setStemDown)
.map(f.StaveNote.bind(f));
f.Beam({
notes: notes.slice(3, 6),
});
f.Tuplet({
notes: notes.slice(0, 3),
options: {
location: Tuplet.LOCATION_BOTTOM,
ratioed: true,
},
});
f.Tuplet({
notes: notes.slice(3, 6),
options: {
location: Tuplet.LOCATION_BOTTOM,
notes_occupied: 1,
},
});
const voice = f
.Voice({ time: { num_beats: 5, beat_value: 8 } })
.setStrict(true)
.addTickables(notes);
new Formatter().joinVoices([voice]).formatToStave([voice], stave);
f.draw();
})();
12連符 再挑戦
先ほどは、ちょっと駄目だったけど、VoiceのBeatっていう概念を取り込むと改善されるのか?ちょっとよくわからんから、まずは12連符を一つをcontextに貼りつける形式でやってみた。new Tuplet()のコンストラクタ引数の第二引数がoptionsオブジェクトになっているので、ここに{ratioed: false}を与える。
(function(){
const set = (key) => (value) => (object) => {
object[key] = value;
return object;
};
const setStemDirection = set('stem_direction');
const {
Factory,
Stave,
StaveNote,
Beam,
Formatter,
Renderer,
Stem,
Articulation,
Tuplet
} = Vex.Flow;
const setStemUp = setStemDirection(Stem.UP);
const setStemDown = setStemDirection(Stem.DOWN);
// const f = VexFlowTests.makeFactory(options, 1500, 195);
const f = new Factory({ renderer: { elementId: 'output4', width: 1500, height: 195 } })
const scale = 0.8;
// Get the rendering context
const ctx = f.getContext();
ctx.scale(scale, scale);
// bar 1
const stave1 = new Stave(10, 50, 500).setContext(ctx).draw();
const notesBar1 = [
f.StaveNote({ keys: ['c/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['d/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['e/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['f/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['c/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['d/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['e/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['f/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['c/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['d/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['e/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['f/4'], duration: '32', stem_direction: 1 }),
f.StaveNote({ keys: ['g/4'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['a/4'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['b/4'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['c/5'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['d/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['e/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['f/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['g/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['a/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['b/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['c/6'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['d/6'], duration: '16', stem_direction: -1 }),
];
let i;
for (i = 0; i < 24; i++) {
notesBar1[i].addModifier(new Articulation('a.').setPosition(4), 0);
notesBar1[i].addModifier(new Articulation('a>').setPosition(4), 0);
if (i === 15) {
//notesBar1[i].addModifier(new Articulation('a@u').setPosition(4), 0);
}
}
const beam1_1 = new Beam(notesBar1.slice(0, 12));
const tuplet1_1 = new Tuplet(notesBar1.slice(0, 12),{ratioed: false});
const beam1_2 = new Beam(notesBar1.slice(12, 16));
const beam2_1 = new Beam(notesBar1.slice(16, 20));
const beam2_2 = new Beam(notesBar1.slice(20, 24));
Formatter.FormatAndDraw(ctx, stave1, notesBar1);
beam1_1.setContext(ctx).draw();
tuplet1_1.setContext(ctx).draw();
beam1_2.setContext(ctx).draw();
beam2_1.setContext(ctx).draw();
beam2_2.setContext(ctx).draw();
// bar 2 - juxtaposing second bar next to first bar
const stave2 = new Stave(510, 50, 500).setContext(ctx).draw();
const notesBar2 = [
f.StaveNote({ keys: ['f/3'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['g/3'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['a/3'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['b/3'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['c/4'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['d/4'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['e/4'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['f/4'], duration: '16', stem_direction: 1 }),
f.StaveNote({ keys: ['g/4'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['a/4'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['b/4'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['c/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['d/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['e/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['f/5'], duration: '16', stem_direction: -1 }),
f.StaveNote({ keys: ['g/5'], duration: '16', stem_direction: -1 }),
];
for (i = 0; i < 16; i++) {
if (i !== 15) {
notesBar2[i].addModifier(new Articulation('a-').setPosition(3), 0);
notesBar2[i].addModifier(new Articulation('a^').setPosition(3), 0);
}
else {
notesBar2[i].addModifier(new Articulation('a-').setPosition(3), 0);
notesBar2[i].addModifier(new Articulation('a^').setPosition(3), 0).addModifier(new Articulation('a@u').setPosition(4), 0);
}
}
const beam3 = new Beam(notesBar2.slice(0, 8));
const beam4 = new Beam(notesBar2.slice(8, 16));
Formatter.FormatAndDraw(ctx, stave2, notesBar2);
beam3.setContext(ctx).draw();
beam4.setContext(ctx).draw();
// bar 3 - juxtaposing second bar next to first bar
const stave3 = new Stave(1010, 50, 100).setContext(ctx).draw();
const notesBar3 = [f.StaveNote({ keys: ['c/4'], duration: 'w', stem_direction: 1 })];
notesBar3[0].addModifier(new Articulation('a-').setPosition(3), 0);
notesBar3[0].addModifier(new Articulation('a>').setPosition(3), 0);
notesBar3[0].addModifier(new Articulation('a@a').setPosition(3), 0);
Formatter.FormatAndDraw(ctx, stave3, notesBar3);
// bar 4 - juxtaposing second bar next to first bar
const stave4 = new Stave(1110, 50, 250).setContext(ctx).draw();
const notesBar4 = [
f.StaveNote({ keys: ['c/5'], duration: 'q', stem_direction: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stem_direction: -1 }),
f.StaveNote({ keys: ['c/5'], duration: 'q', stem_direction: -1 }),
f.StaveNote({ keys: ['a/5'], duration: 'q', stem_direction: -1 }),
];
for (i = 0; i < 4; i++) {
let position1 = 3;
if (i > 1) {
position1 = 4;
}
notesBar4[i].addModifier(new Articulation('a-').setPosition(position1), 0);
}
Formatter.FormatAndDraw(ctx, stave4, notesBar4);
})();
そうすると、譜例のように12連符が作れた!ratioed: falseで連符の比率表示の後ろが消える。beatを使うときはたぶん、この比率の概念がうまくうまるように指定しないと非表示になっちゃうんだろうね。でも、自分には今のところできないね。ratioedとおなじところで、beats_occupied: 数値で、音符の何個分の長さに対して連符にするのかを指定するっていってるんだけど、うまくいかないのだ。もうちょっとやりこんでみる。VexFlowの中の人はもうちょっと説明書をちゃんと作るべきだな。でないと使う人が減る一方だと思うのだけど。でも、この方法なら比率の後ろの長さを指定しなくて済むので、普通の楽譜は大体つくれることになる。ピアノでショパンとかの譜面だと8分音符何個分の連符という表示をせざるを得ない部分を見たことがある。あとは法則どおりなので、比率を明示する必要があまり生じない。でもbeatは覚えないとスコアの縦のラインがそろわない気がするね。
テスト2