Skip to content

Commit

Permalink
Merge pull request #1 from PolymerLabs/easyscore-basics
Browse files Browse the repository at this point in the history
Wrap Stave and Voice in web components
  • Loading branch information
ywsang authored Jun 26, 2020
2 parents 0c51ce4 + b73502a commit ec64a48
Show file tree
Hide file tree
Showing 7 changed files with 3,733 additions and 385 deletions.
15 changes: 15 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// EasyScore API Example

import Vex from './src/index.js';
const VF = Vex.Flow;

// Create an SVG renderer and attach it to the DIV element named "boo".
var vf = new VF.Factory({renderer: {elementId: null}});
var score = vf.EasyScore();
var system = vf.System();

system.addStave({
voices: [score.voice(score.notes('C#5/q, B4, A4, G#4'))]
}).addClef('treble').addTimeSignature('4/4');

vf.draw();
20 changes: 20 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<html>
<head>
<title>Vexflow Component</title>
<script type="module" src='./wc-src/vf-score.js'></script>
<script type="module" src='./wc-src/vf-stave.js'></script>
<script type="module" src='./wc-src/vf-voice.js'></script>
</head>
<body>
<!-- For testing the EasyScore API example
<div id="boo"></div>
<script type="module" src="./app.js"></script> -->

<vf-score>
<vf-stave clef='treble' timeSig='4/4' keySig='Bb'>
<vf-voice autoBeam>C5/q, (C4 Eb4 G4)/q, A4, G4/16, A4, B4, C5</vf-voice>
<vf-voice stem='up'>C#4/h, C#4</vf-voice>
</vf-stave>
</vf-score>
</body>
</html>
3,850 changes: 3,466 additions & 384 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,8 @@
"notation",
"guitar",
"tablature"
]
],
"dependencies": {
"es-dev-server": "^1.54.1"
}
}
44 changes: 44 additions & 0 deletions wc-src/vf-score.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Vex from '../src/index.js';

const template = document.createElement('template');
template.innerHTML = `
<div id='vf-score'>
<slot></slot>
</div>
`

export class VFScore extends HTMLElement {
constructor() {
super();

this.attachShadow({ mode:'open' });
this.shadowRoot.appendChild(document.importNode(template.content, true));

this.addEventListener('vfVoiceReady', this.setFactory);
this.addEventListener('vfStaveReady', this.setFactory);
}

connectedCallback() {
this.setupVexflow(this.getAttribute('width') || 500, this.getAttribute('height') || 200);
this.setupFactory();
}

setupVexflow(width, height) {
const div = this.shadowRoot.getElementById('vf-score');
const renderer = new Vex.Flow.Renderer(div, Vex.Flow.Renderer.Backends.SVG);
renderer.resize(width, height);
this.context = renderer.getContext();
}

setupFactory() {
this.vf = new Vex.Flow.Factory({renderer: {elementId: null}});
this.vf.setContext(this.context);
}

/** Sets the factory instance of the component that dispatched the event */
setFactory = () => {
event.target.vf = this.vf;
}
}

window.customElements.define('vf-score', VFScore);
113 changes: 113 additions & 0 deletions wc-src/vf-stave.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import Vex from '../src/index.js';
import './vf-score';

const template = document.createElement('template');
template.innerHTML = `
<slot></slot>
`;

export class VFStave extends HTMLElement {
constructor() {
super();

// Defaults
this.voices = [];
this.beams = [];
this._vf = undefined;

this.attachShadow({ mode:'open' });
this.shadowRoot.appendChild(document.importNode(template.content, true));

this.addEventListener('notesCreated', this.addVoice);
this.addEventListener('vfVoiceReady', this.setScore);
}

connectedCallback() {
this.clef = this.getAttribute('clef');
this.timeSig = this.getAttribute('timeSig');
this.keySig = this.getAttribute('keySig');

const vfStaveReadyEvent = new CustomEvent('vfStaveReady', { bubbles: true });
this.dispatchEvent(vfStaveReadyEvent);

this.shadowRoot.querySelector('slot').addEventListener('slotchange', this.registerVoices);
}

disconnectedCallback() {
this.shadowRoot.querySelector('slot').removeEventListener('slotchange', this.registerVoices);
}

set vf(value) {
this._vf = value;
this.setupStave();
}

setupStave() {
this.score = this._vf.EasyScore();
this.score.set({
clef: this.clef || 'treble',
time: this.timeSig || '4/4'
});

this.stave = this._vf.Stave( { x: 10, y: 40, width: 400 });

if (this.clef) {
this.stave.addClef(this.clef);
}

if (this.timeSig) {
this.stave.addTimeSignature(this.timeSig);
}

if (this.keySig) {
this.stave.addKeySignature(this.keySig);
}

this.stave.draw();
}

/** slotchange event listener */
registerVoices = () => {
const voiceSlots = this.shadowRoot.querySelector('slot').assignedElements().filter( e => e.nodeName === 'VF-VOICE');
this.numVoices = voiceSlots.length;

if (this.voices.length === this.numVoices) {
this.formatAndDrawVoices();
}
}

/** Event listener when vf-voice returns notes */
addVoice = (e) => {
const notes = e.detail.notes;
const beams = e.detail.beams;
const voice = this.createVoiceFromNotes(notes);

this.voices.push(voice);
this.beams = this.beams.concat(beams);

// Make sure all voices are created first, then format & draw to make sure alignment is correct
if (this.voices.length === this.numVoices) {
this.formatAndDrawVoices();
}
}

createVoiceFromNotes(staveNotes) {
return this.score.voice(staveNotes);
}

formatAndDrawVoices() {
var formatter = new Vex.Flow.Formatter()
formatter.joinVoices(this.voices);
formatter.formatToStave(this.voices, this.stave);
this._vf.draw();
}


/** Sets the score instance of the component that dispatched the event */
setScore = () => {
event.target.score = this.score;
}

}

window.customElements.define('vf-stave', VFStave);
71 changes: 71 additions & 0 deletions wc-src/vf-voice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Vex from '../src/index.js';

const template = document.createElement('template');
template.innerHTML = `
`;

export class VFVoice extends HTMLElement {
constructor() {
super();

this.attachShadow({ mode:'open' });
this.shadowRoot.appendChild(document.importNode(template.content, true));

// Defaults
this.stem = 'up';
this.autoBeam = false;
this.notes = [];
this.beams = [];

this._vf = undefined;
this._score = undefined;
}

connectedCallback() {
this.stem = this.getAttribute('stem') || this.stem;
this.autoBeam = this.hasAttribute('autoBeam');
this.notesText = this.textContent.trim();

const vfVoiceReadyEvent = new CustomEvent('vfVoiceReady', { bubbles: true });
this.dispatchEvent(vfVoiceReadyEvent);
}

set vf(value) {
this._vf = value;
this.createNotes();
}

set score(value) {
this._score = value;
this.createNotes();
}

createNotes() {
if (this._vf && this._score) {
const notes = this.createNotesFromText();
this.notes.push(...notes);
if (this.autoBeam) {
this.beams.push(...this.autoGenerateBeams(notes));
}

const notesAndBeamsCreatedEvent = new CustomEvent('notesCreated', { bubbles: true, detail: { notes: this.notes, beams: this.beams } });
this.dispatchEvent(notesAndBeamsCreatedEvent);
}
}

createNotesFromText() {
this._score.set({ stem: this.stem });
const staveNotes = this._score.notes(this.notesText);
return staveNotes;
}

autoGenerateBeams(notes) {
const beams = Vex.Flow.Beam.generateBeams(notes);
beams.forEach( beam => {
this._vf.renderQ.push(beam);
})
return beams;
}
}

window.customElements.define('vf-voice', VFVoice);

0 comments on commit ec64a48

Please sign in to comment.