@stringsync/vexml
Version:
MusicXML to Vexflow
901 lines (900 loc) • 26.4 kB
JavaScript
/* eslint-disable @typescript-eslint/no-unused-vars */
import { NamedElement } from './namedelement';
/**
* A wrapper around NamedElement that performs mutative actions.
*
* This allows callers to exclusively work with NamedElements, as opposed to casting NamedElements to their native form.
* It also prevents the need for NamedElement to have an mutative actions.
*/
class NamedElementEditor {
element;
constructor(element) {
this.element = element;
}
/** Appends the elements to the wrapped element as direct children. */
append(...elements) {
this.element.native().append(...elements.map((element) => element.native()));
}
/** Sets the text content of the wrapped element. */
setTextContent(textContent) {
this.element.native().textContent = textContent;
}
/** Sets the attribute of the wrapped element. */
setAttribute(name, value) {
this.element.native().setAttribute(name, value);
}
}
/** Creates a document node. */
export const createDocument = () => document.implementation.createDocument(null, null);
/** Creates an XML element with the specified tag name. */
export const createElement = (tagName) => {
return createDocument().createElement(tagName);
};
/** Creates a NamedElement with the specified tag name. */
export const createNamedElement = (tagName) => {
return NamedElement.of(createElement(tagName));
};
/**
* Creates an element factory.
*
* Factories are invoked by invoking the returned function with the arguments.
* @example
* const fooFactory = createNamedElementFactory<'foo', { bar: string }>('foo', (e, { bar }) => {
* if (typeof bar === 'string') {
* e.setTextContent(bar);
* }
* });
* const fooNamedElement = fooFactory('bar');
*/
const createNamedElementFactory = (tagName, builder) => {
return (args) => {
const element = createNamedElement(tagName);
builder(new NamedElementEditor(element), args ?? {});
return element;
};
};
export const musicXML = (scorePartwise) => {
const root = createDocument();
root.appendChild(scorePartwise.native());
return root;
};
export const scorePartwise = createNamedElementFactory('score-partwise', (e, { parts, partList, defaults }) => {
if (parts) {
e.append(...parts);
}
if (partList) {
e.append(partList);
}
if (defaults) {
e.append(defaults);
}
});
export const partList = createNamedElementFactory('part-list', (e, { scoreParts }) => {
if (scoreParts) {
e.append(...scoreParts);
}
});
export const scorePart = createNamedElementFactory('score-part', (e, { id, partName }) => {
if (id) {
e.setAttribute('id', id);
}
if (partName) {
e.append(partName);
}
});
export const partName = createNamedElementFactory('part-name', (e, { textContent }) => {
if (textContent) {
e.setTextContent(textContent);
}
});
export const part = createNamedElementFactory('part', (e, { id, measures }) => {
if (id) {
e.setAttribute('id', id);
}
if (measures) {
e.append(...measures);
}
});
export const measure = createNamedElementFactory('measure', (e, { width, entries, implicit, number, notes, attributes, barlines, prints }) => {
if (entries) {
e.append(...entries);
}
if (notes) {
e.append(...notes);
}
if (attributes) {
e.append(...attributes);
}
if (barlines) {
e.append(...barlines);
}
if (prints) {
e.append(...prints);
}
if (implicit) {
e.setAttribute('implicit', implicit);
}
if (typeof width === 'number') {
e.setAttribute('width', width.toString());
}
if (typeof number === 'string') {
e.setAttribute('number', number);
}
});
export const note = createNamedElementFactory('note', (e, { printObject, type, grace, stem, dots, rest, pitch, accidental, timeModification, notehead, duration, notations, voice, staff, beams, chord, lyrics, }) => {
if (typeof printObject === 'string') {
e.setAttribute('print-object', printObject);
}
if (grace) {
e.append(grace);
}
if (chord) {
e.append(chord);
}
if (pitch) {
e.append(pitch);
}
if (rest) {
e.append(rest);
}
if (duration) {
e.append(duration);
}
if (voice) {
e.append(voice);
}
if (type) {
e.append(type);
}
if (dots) {
e.append(...dots);
}
if (accidental) {
e.append(accidental);
}
if (timeModification) {
e.append(timeModification);
}
if (stem) {
e.append(stem);
}
if (notehead) {
e.append(notehead);
}
if (staff) {
e.append(staff);
}
if (beams) {
e.append(...beams);
}
if (notations) {
e.append(...notations);
}
if (lyrics) {
e.append(...lyrics);
}
});
export const backup = createNamedElementFactory('backup', (e, { duration }) => {
if (duration) {
e.append(duration);
}
});
export const forward = createNamedElementFactory('forward', (e, { duration }) => {
if (duration) {
e.append(duration);
}
});
export const type = createNamedElementFactory('type', (e, { textContent }) => {
if (textContent) {
e.setTextContent(textContent);
}
});
export const attributes = createNamedElementFactory('attributes', (e, { divisions, staves, clefs, times, keys, staffDetails, measureStyles }) => {
if (divisions) {
e.append(divisions);
}
if (keys) {
e.append(...keys);
}
if (times) {
e.append(...times);
}
if (clefs) {
e.append(...clefs);
}
if (staves) {
e.append(staves);
}
if (staffDetails) {
e.append(...staffDetails);
}
if (measureStyles) {
e.append(...measureStyles);
}
});
export const divisions = createNamedElementFactory('divisions', (e, { positiveDivisions }) => {
if (typeof positiveDivisions === 'number') {
e.setTextContent(positiveDivisions.toString());
}
});
export const print = createNamedElementFactory('print', (e, { newSystem, newPage, staffLayouts, systemLayout }) => {
if (typeof newSystem === 'boolean') {
e.setAttribute('new-system', newSystem ? 'yes' : 'no');
}
if (typeof newPage === 'boolean') {
e.setAttribute('new-page', newPage ? 'yes' : 'no');
}
if (staffLayouts) {
e.append(...staffLayouts);
}
if (systemLayout) {
e.append(systemLayout);
}
});
export const defaults = createNamedElementFactory('defaults', (e, { staffLayouts, systemLayout }) => {
if (staffLayouts) {
e.append(...staffLayouts);
}
if (systemLayout) {
e.append(systemLayout);
}
});
export const staffLayout = createNamedElementFactory('staff-layout', (e, { number, staffDistance }) => {
if (typeof number === 'number') {
e.setAttribute('number', number.toString());
}
if (staffDistance) {
e.append(staffDistance);
}
});
export const staffDistance = createNamedElementFactory('staff-distance', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const systemLayout = createNamedElementFactory('system-layout', (e, { systemMargins, systemDistance, topSystemDistance, systemDividers }) => {
if (systemMargins) {
e.append(systemMargins);
}
if (systemDistance) {
e.append(systemDistance);
}
if (topSystemDistance) {
e.append(topSystemDistance);
}
if (systemDividers) {
e.append(systemDividers);
}
});
export const systemMargins = createNamedElementFactory('system-margins', (e, { leftMargin, rightMargin }) => {
if (leftMargin) {
e.append(leftMargin);
}
if (rightMargin) {
e.append(rightMargin);
}
});
export const leftMargin = createNamedElementFactory('left-margin', (e, { tenths }) => {
if (typeof tenths === 'number') {
e.setTextContent(tenths.toString());
}
});
export const rightMargin = createNamedElementFactory('right-margin', (e, { tenths }) => {
if (typeof tenths === 'number') {
e.setTextContent(tenths.toString());
}
});
export const systemDistance = createNamedElementFactory('system-distance', (e, { tenths }) => {
if (typeof tenths === 'number') {
e.setTextContent(tenths.toString());
}
});
export const topSystemDistance = createNamedElementFactory('top-system-distance', (e, { tenths }) => {
if (typeof tenths === 'number') {
e.setTextContent(tenths.toString());
}
});
export const direction = createNamedElementFactory('direction', (e, { placement, types }) => {
if (placement) {
e.setAttribute('placement', placement);
}
if (types) {
e.append(...types);
}
});
export const directionType = createNamedElementFactory('direction-type', (e, { segnos, codas, wedge, pedal, metronome, octaveShift, tokens }) => {
if (segnos) {
e.append(...segnos);
}
if (codas) {
e.append(...codas);
}
if (wedge) {
e.append(wedge);
}
if (pedal) {
e.append(pedal);
}
if (metronome) {
e.append(metronome);
}
if (octaveShift) {
e.append(octaveShift);
}
if (tokens) {
e.append(...tokens);
}
});
export const words = createNamedElementFactory('words', (e, { content }) => {
if (content) {
e.setTextContent(content);
}
});
export const symbolic = createNamedElementFactory('symbol', (e, { smuflGlyphName }) => {
if (smuflGlyphName) {
e.setTextContent(smuflGlyphName);
}
});
export const metronome = createNamedElementFactory('metronome', (e, { parentheses, content }) => {
if (typeof parentheses === 'string') {
e.setAttribute('parentheses', parentheses);
}
if (content) {
e.append(...content);
}
});
export const beatUnit = createNamedElementFactory('beat-unit', (e, { noteTypeValue }) => {
if (noteTypeValue) {
e.setTextContent(noteTypeValue);
}
});
export const beatUnitDot = createNamedElementFactory('beat-unit-dot', () => { });
export const perMinute = createNamedElementFactory('per-minute', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const barline = createNamedElementFactory('barline', (e, { location, barStyle, repeat, ending }) => {
if (location) {
e.setAttribute('location', location);
}
if (barStyle) {
e.append(barStyle);
}
if (repeat) {
e.append(repeat);
}
if (ending) {
e.append(ending);
}
});
export const staves = createNamedElementFactory('staves', (e, { staveCount }) => {
if (typeof staveCount === 'number') {
e.setTextContent(staveCount.toString());
}
});
export const barStyle = createNamedElementFactory('bar-style', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const repeat = createNamedElementFactory('repeat', (e, { direction }) => {
if (direction) {
e.setAttribute('direction', direction);
}
});
export const ending = createNamedElementFactory('ending', (e, { number, type, textContent }) => {
if (number) {
e.setAttribute('number', number);
}
if (type) {
e.setAttribute('type', type);
}
if (textContent) {
e.setTextContent(textContent);
}
});
export const stem = createNamedElementFactory('stem', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const dot = createNamedElementFactory('dot', (_, __) => {
// noop
});
export const rest = createNamedElementFactory('rest', (e, { displayStep, displayOctave }) => {
if (displayStep) {
e.append(displayStep);
}
if (displayOctave) {
e.append(displayOctave);
}
});
export const displayStep = createNamedElementFactory('display-step', (e, { step }) => {
if (step) {
e.setTextContent(step);
}
});
export const displayOctave = createNamedElementFactory('display-octave', (e, { octave }) => {
if (octave) {
e.setTextContent(octave);
}
});
export const pitch = createNamedElementFactory('pitch', (e, { step, alter, octave }) => {
if (step) {
e.append(step);
}
if (alter) {
e.append(alter);
}
if (octave) {
e.append(octave);
}
});
export const step = createNamedElementFactory('step', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const alter = createNamedElementFactory('alter', (e, { value }) => {
if (typeof value === 'number') {
e.setTextContent(value.toString());
}
});
export const octave = createNamedElementFactory('octave', (e, { value }) => {
if (value) {
e.setTextContent(value.toString());
}
});
export const accidental = createNamedElementFactory('accidental', (e, { value, cautionary }) => {
if (value) {
e.setTextContent(value);
}
if (cautionary) {
e.setAttribute('cautionary', cautionary);
}
});
export const grace = createNamedElementFactory('grace', (e, { slash }) => {
if (slash) {
e.setAttribute('slash', slash);
}
});
export const duration = createNamedElementFactory('duration', (e, { positiveDivisions }) => {
if (typeof positiveDivisions === 'number') {
e.setTextContent(positiveDivisions.toString());
}
});
export const key = createNamedElementFactory('key', (e, { fifths, mode }) => {
if (fifths) {
e.append(fifths);
}
if (mode) {
e.append(mode);
}
});
export const fifths = createNamedElementFactory('fifths', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const mode = createNamedElementFactory('mode', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const time = createNamedElementFactory('time', (e, { staveNumber, symbol, times, senzaMisura }) => {
if (typeof staveNumber === 'number') {
e.setAttribute('number', staveNumber.toString());
}
if (symbol) {
e.setAttribute('symbol', symbol);
}
if (times) {
for (const { beats, beatType } of times) {
if (beats) {
e.append(beats);
}
if (beatType) {
e.append(beatType);
}
}
}
if (senzaMisura) {
e.append(senzaMisura);
}
});
export const senzaMisura = createNamedElementFactory('senza-misura', (e, { content }) => {
if (content) {
e.setTextContent(content);
}
});
export const clef = createNamedElementFactory('clef', (e, { number, sign, line, clefOctaveChange }) => {
if (typeof number === 'number') {
e.setAttribute('number', number.toString());
}
if (sign) {
e.append(sign);
}
if (line) {
e.append(line);
}
if (clefOctaveChange) {
e.append(clefOctaveChange);
}
});
export const coda = createNamedElementFactory('coda', (_, __) => {
// noop
});
export const segno = createNamedElementFactory('segno', (_, __) => {
// noop
});
export const sign = createNamedElementFactory('sign', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const line = createNamedElementFactory('line', (e, { value }) => {
if (typeof value === 'number') {
e.setTextContent(value.toString());
}
});
export const clefOctaveChange = createNamedElementFactory('clef-octave-change', (e, { value }) => {
if (typeof value === 'number') {
e.setTextContent(value.toString());
}
});
export const beats = createNamedElementFactory('beats', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const beatType = createNamedElementFactory('beat-type', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const lyric = createNamedElementFactory('lyric', (e, { number, components }) => {
if (typeof number === 'number') {
e.setAttribute('number', number.toString());
}
if (components) {
e.append(...components);
}
});
export const syllabic = createNamedElementFactory('syllabic', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const text = createNamedElementFactory('text', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const elision = createNamedElementFactory('elision', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const notations = createNamedElementFactory('notations', (e, { tieds, slurs, tuplets, arpeggiate, ornaments, fermatas, articulations, accidentalMarks }) => {
if (tieds) {
e.append(...tieds);
}
if (slurs) {
e.append(...slurs);
}
if (tuplets) {
e.append(...tuplets);
}
if (arpeggiate) {
e.append(arpeggiate);
}
if (ornaments) {
e.append(...ornaments);
}
if (articulations) {
e.append(...articulations);
}
if (fermatas) {
e.append(...fermatas);
}
if (accidentalMarks) {
e.append(...accidentalMarks);
}
});
export const arpeggiate = createNamedElementFactory('arpeggiate', (e, { direction }) => {
if (direction) {
e.setAttribute('direction', direction);
}
});
export const voice = createNamedElementFactory('voice', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const staff = createNamedElementFactory('staff', (e, { number }) => {
if (typeof number === 'number') {
e.setTextContent(number.toString());
}
});
export const notehead = createNamedElementFactory('notehead', (e, { value }) => {
if (value) {
e.setTextContent(value);
}
});
export const chord = createNamedElementFactory('chord', (_, __) => {
// noop
});
export const beam = createNamedElementFactory('beam', (e, { number, beamValue }) => {
if (typeof number === 'number') {
e.setAttribute('number', number.toString());
}
if (typeof beamValue === 'string') {
e.setTextContent(beamValue);
}
});
export const staffDetails = createNamedElementFactory('staff-details', (e, { number, staffType, staffLines }) => {
if (typeof number === 'number') {
e.setAttribute('number', number.toString());
}
if (staffType) {
e.append(staffType);
}
if (staffLines) {
e.append(staffLines);
}
});
export const measureStyle = createNamedElementFactory('measure-style', (e, { staffNumber, multipleRest }) => {
if (typeof staffNumber === 'number') {
e.setAttribute('number', staffNumber.toString());
}
if (multipleRest) {
e.append(multipleRest);
}
});
export const multipleRest = createNamedElementFactory('multiple-rest', (e, { multipleRestCount }) => {
if (typeof multipleRestCount === 'number') {
e.setTextContent(multipleRestCount.toString());
}
});
export const staffLines = createNamedElementFactory('staff-lines', (e, { value }) => {
if (typeof value === 'number') {
e.setTextContent(value.toString());
}
});
export const staffType = createNamedElementFactory('staff-type', (e, { value }) => {
if (typeof value === 'string') {
e.setTextContent(value);
}
});
export const tuplet = createNamedElementFactory('tuplet', (e, { type, placement, showNumber }) => {
if (type) {
e.setAttribute('type', type);
}
if (placement) {
e.setAttribute('placement', placement);
}
if (showNumber) {
e.setAttribute('show-number', showNumber);
}
});
export const timeModification = createNamedElementFactory('time-modification', (e, { actualNotes, normalNotes }) => {
if (actualNotes) {
e.append(actualNotes);
}
if (normalNotes) {
e.append(normalNotes);
}
});
export const actualNotes = createNamedElementFactory('actual-notes', (e, { value }) => {
if (typeof value === 'number') {
e.setTextContent(value.toString());
}
});
export const normalNotes = createNamedElementFactory('normal-notes', (e, { value }) => {
if (typeof value === 'number') {
e.setTextContent(value.toString());
}
});
export const slur = createNamedElementFactory('slur', (e, { type, placement, number, lineType }) => {
if (type) {
e.setAttribute('type', type);
}
if (placement) {
e.setAttribute('placement', placement);
}
if (typeof number === 'number') {
e.setAttribute('number', number.toString());
}
if (lineType) {
e.setAttribute('line-type', lineType);
}
});
export const tied = createNamedElementFactory('tied', (e, { type, placement, number, lineType }) => {
if (type) {
e.setAttribute('type', type);
}
if (placement) {
e.setAttribute('placement', placement);
}
if (typeof number === 'number') {
e.setAttribute('number', number.toString());
}
if (lineType) {
e.setAttribute('line-type', lineType);
}
});
export const wedge = createNamedElementFactory('wedge', (e, { type, spread }) => {
if (type) {
e.setAttribute('type', type);
}
if (typeof spread === 'number') {
e.setAttribute('spread', spread.toString());
}
});
export const ornaments = createNamedElementFactory('ornaments', (e, { contents }) => {
if (contents) {
e.append(...contents);
}
});
export const trillMark = createNamedElementFactory('trill-mark', (e) => { });
export const wavyLine = createNamedElementFactory('wavy-line', (e, { number, type }) => {
if (typeof number === 'number') {
e.setAttribute('number', number.toString());
}
if (type) {
e.setAttribute('type', type);
}
});
export const octaveShift = createNamedElementFactory('octave-shift', (e, { type, size }) => {
if (type) {
e.setAttribute('type', type);
}
if (typeof size === 'number') {
e.setAttribute('size', size.toString());
}
});
export const pedal = createNamedElementFactory('pedal', (e, { type, line, sign }) => {
if (type) {
e.setAttribute('type', type);
}
if (line) {
e.setAttribute('line', line);
}
if (sign) {
e.setAttribute('sign', sign);
}
});
export const container = createNamedElementFactory('container', (e, { rootfiles }) => {
if (rootfiles) {
e.append(rootfiles);
}
});
export const rootfiles = createNamedElementFactory('rootfiles', (e, { rootfiles }) => {
if (rootfiles) {
e.append(...rootfiles);
}
});
export const rootfile = createNamedElementFactory('rootfile', (e, { fullPath, mediaType }) => {
if (fullPath) {
e.setAttribute('full-path', fullPath);
}
if (mediaType) {
e.setAttribute('media-type', mediaType);
}
});
export const fermata = createNamedElementFactory('fermata', (e, { shape, type }) => {
if (shape) {
e.setTextContent(shape);
}
if (type) {
e.setAttribute('type', type);
}
});
export const articulations = createNamedElementFactory('articulations', (e, { accents, strongAccents, staccatos, tenutos, detachedLegatos, staccatissimos, scoops, plops, doits, falloffs, breathMarks, }) => {
if (accents) {
e.append(...accents);
}
if (strongAccents) {
e.append(...strongAccents);
}
if (staccatos) {
e.append(...staccatos);
}
if (tenutos) {
e.append(...tenutos);
}
if (detachedLegatos) {
e.append(...detachedLegatos);
}
if (staccatissimos) {
e.append(...staccatissimos);
}
if (scoops) {
e.append(...scoops);
}
if (plops) {
e.append(...plops);
}
if (doits) {
e.append(...doits);
}
if (falloffs) {
e.append(...falloffs);
}
if (breathMarks) {
e.append(...breathMarks);
}
});
export const accent = createNamedElementFactory('accent', (e, { placement }) => {
if (placement) {
e.setAttribute('placement', placement);
}
});
export const strongAccent = createNamedElementFactory('strong-accent', (e, { placement }) => {
if (placement) {
e.setAttribute('placement', placement);
}
});
export const staccato = createNamedElementFactory('staccato', (e, { placement }) => {
if (placement) {
e.setAttribute('placement', placement);
}
});
export const tenuto = createNamedElementFactory('tenuto', (e, { placement }) => {
if (placement) {
e.setAttribute('placement', placement);
}
});
export const detachedLegato = createNamedElementFactory('detached-legato', (e, { placement }) => {
if (placement) {
e.setAttribute('placement', placement);
}
});
export const staccatissimo = createNamedElementFactory('staccatissimo', (e, { placement }) => {
if (placement) {
e.setAttribute('placement', placement);
}
});
export const scoop = createNamedElementFactory('scoop', (e, { placement, lineType }) => {
if (placement) {
e.setAttribute('placement', placement);
}
if (lineType) {
e.setAttribute('line-type', lineType);
}
});
export const plop = createNamedElementFactory('plop', (e, { placement, lineType }) => {
if (placement) {
e.setAttribute('placement', placement);
}
if (lineType) {
e.setAttribute('line-type', lineType);
}
});
export const doit = createNamedElementFactory('doit', (e, { placement, lineType }) => {
if (placement) {
e.setAttribute('placement', placement);
}
if (lineType) {
e.setAttribute('line-type', lineType);
}
});
export const falloff = createNamedElementFactory('falloff', (e, { placement, lineType }) => {
if (placement) {
e.setAttribute('placement', placement);
}
if (lineType) {
e.setAttribute('line-type', lineType);
}
});
export const breathMark = createNamedElementFactory('breath-mark', (e, { placement }) => {
if (placement) {
e.setAttribute('placement', placement);
}
});
export const accidentalMark = createNamedElementFactory('accidental-mark', (e, { type }) => {
if (type) {
e.setTextContent(type);
}
});