/**
* @class
* @classdesc Encapsulates functions to transform internal AlphaTexStructure back into AlphaTex
*/
class ConvertToTex {
/**
* Converts provided AlphaTexStructure into AlphaTex
* @param {AlphaTexStructure} alphaTexStructure The AlphaTexStructure to be converted
* @param {object} lyrics Lyrics per track. Expected mapping track index number to lyric string
* @returns {string} The generated AlphaTex
*/
visitAlphaTexStructure(alphaTexStructure, lyrics) {
let tex = "";
alphaTexStructure.attributesStr.forEach((value, key, map) => {
tex += "\\" + key + " " + value + "\n";
});
alphaTexStructure.attributesInt.forEach((value, key, map) => {
tex += "\\" + key + " " + value + "\n";
});
tex += ".\n";
let lyricIndex = 0;
alphaTexStructure.tracks.forEach((track) => {
tex += this.visitTrack(track, lyrics[lyricIndex]);
lyricIndex++;
});
return tex;
}
/**
* Converts the provided Track to AlphaTex
* @param {Track} track The Track to be converted
* @param {object} lyrics Lyrics per track. Expected mapping track index number to lyric string
* @returns {string} The generated AlphaTex
*/
visitTrack(track, lyrics) {
let tex = "\\track " + track.getTrackName() + "\n";
tex += this.visitTrackData(track.trackData, lyrics);
return tex;
}
/**
* Converts the provided TrackData to AlphaTex
* @param {TrackMetaData} trackData The TrackMetaData to be converted
* @param {object} lyrics Lyrics per track. Expected mapping track index number to lyric string
* @returns {string} The generated AlphaTex
*/
visitTrackData(trackData, lyrics) {
let tex = "";
trackData.staffs.forEach((staff) => {
tex += this.visitStaff(staff);
});
return tex;
}
/**
* Converts the provided Staff to AlphaTex
* @param {Staff} staff The Staff to be converted
* @param {object} lyrics Lyrics per track. Expected mapping track index number to lyric string
* @returns {string} The generated AlphaTex
*/
visitStaff(staff, lyrics) {
let tex = "\\staff ";
tex += "{" + staff.staffOption + "}";
tex += this.attributesStrTex(staff.attributesStr) + "\n";
if (lyrics) {
tex += "\\lyrics " + lyrics + "\n";
}
staff.measures.forEach((measure) => {
tex += this.visitMeasure(measure);
});
return tex;
}
/**
* Converts the provided attributes String mapping to AlphaTex
* @param {Map} attributesStr The attribute String mapping to convert. Map String attributes to String values
* @returns {string} The generated AlphaTex
*/
attributesStrTex(attributesStr) {
let tex = "";
attributesStr.forEach((value, key, map) => {
if (tex.length > 0) {
tex += " ";
}
if (key === "keySignature") {
tex += "\\ks " + value;
} else {
tex += "\\" + key + " " + value;
}
});
return tex;
}
/**
* Converts the provided Measure to AlphaTex
* @param {Measure} measure The Measure to be converted
* @returns {string} The generated AlphaTex
*/
visitMeasure(measure) {
let tex = "";
tex += this.attributesStrTex(measure.attributesStr);
let ts = [-1, -1];
measure.attributesInt.forEach((value, key, map) => {
if (key == "tsTop") {
ts[0] = value;
if (ts[1] != -1) {
tex += " \\ts " + ts[0] + " " + ts[1];
ts[0] = -1;
ts[1] = -1;
}
} else if (key == "tsBottom") {
ts[1] = value;
if (ts[0] != -1) {
tex += " \\ts " + ts[0] + " " + ts[1];
ts[0] = -1;
ts[1] = -1;
}
} else {
tex += " \\" + key + " " + value;
}
});
measure.chords.forEach((chord) => {
if (tex.length > 0) {
tex += " ";
}
tex += this.visitChord(chord);
});
tex += " |\n";
return tex;
}
/**
* Converts the provided Map to a space separated list of key and value pairs
* @param {Map} effects Map to be converted
* @returns {string} The space separated list of key and value pairs
*/
getEffectText(effects) {
let effectText = "";
effects.forEach((value, key, map) => {
if (effectText.length > 0) {
effectText += " ";
}
if (value === true) {
effectText += key;
} else {
effectText += key + " " + value;
}
});
return effectText;
}
/**
* Converts the provided Chord to AlphaTex
* @param {Chord} chord The Chord to be converted
* @returns {string} The generated AlphaTex
*/
visitChord(chord) {
let tex = "";
if (chord.notes.length == 1) {
tex += this.visitNote(chord.notes[0], false);
} else {
tex += "(";
let duration = -1;
let effects = new Map();
let noteText = "";
chord.notes.forEach((note) => {
let noteObj = this.visitNote(note, true);
duration = noteObj.duration;
noteObj.effects.after.forEach((value, key, map) => {
effects.set(key, value);
});
if (noteText.length > 0) {
noteText += " ";
}
noteText += noteObj.tex;
let effectTextBefore = this.getEffectText(noteObj.effects.before);
if (effectTextBefore.length > 0) {
noteText += "{" + effectTextBefore + "}";
}
});
tex += noteText + ")";
if (duration !== -1) {
tex += "." + duration;
}
let effectText = this.getEffectText(effects);
if (effectText.length > 0) {
tex += "{" + effectText + "}";
}
}
return tex;
}
/**
* Converts the provided effect back to its AlphaTex version
* @param {string} effect The effect String to be converted (expects one of: tied, dotted, crescendo, decrescendo)
* @returns {string} The AlphaTex version of the effect
*/
effectToSymbol(effect) {
if (effect === "tied") {
return "-";
} else if (effect === "dotted") {
return "d";
} else if (effect === "crescendo") {
return "cre";
} else if (effect === "decrescendo") {
return "dec";
} else {
console.log("ERROR- what:", effect);
return effect;
}
}
/**
* @typedef {Object} NotePackage
* @property {string} tex The generated AlphaTex
* @property {number} duration The duration of the note
* @property {object} effects
* @property {Map} effects.effectsBefore The effects needed to be placed before the "." for the note
* @property {Map} effects.effectsAfter The effects needed to be placed after the duration for the note
*/
/**
* Converts the provided Note to AlphaTex
* @param {Note} note The Note to be translated
* @param {boolean} splitInfo Boolean set to true if the translated information should be returned as an object (split into pieces)
* @returns {(NotePackage|string)} If splitInfo is true, returns a NotePackage. Else, returns the generated AlphaTex
*/
visitNote(note, splitInfo) {
let tex = note.note;
if (note.octave > 0) {
tex += note.octave;
}
let effectsBefore = new Map();
let effectsAfter = new Map();
if (note.beatEffects) {
note.beatEffects.forEach((value1, value2, set) => {
let rel = value1.split(" ");
if (Array.isArray(rel)) {
if (rel[0] === "tuplet") {
effectsAfter.set("tu", rel[1]);
} else if (rel[0] === "dynamic") {
effectsAfter.set("dy", rel[1]);
} else {
let symbol = this.effectToSymbol(rel[0]);
if (note.octave > 0) {
if (symbol === "-" || symbol === "d") {
effectsBefore.set(symbol, true);
} else {
effectsAfter.set(symbol, true);
}
} else {
effectsAfter.set(symbol, true);
}
}
} else {
let symbol = this.effectToSymbol(rel);
if (note.octave > 0) {
if (symbol === "-" || symbol === "d") {
effectsBefore.set(symbol, true);
} else {
effectsAfter.set(symbol, true);
}
} else {
effectsAfter.set(symbol, true);
}
}
});
}
if (splitInfo) {
let retObj = {
tex,
duration: note.duration,
effects: {
before: effectsBefore,
after: effectsAfter
}
}
return retObj;
} else {
let effectTextBefore = this.getEffectText(effectsBefore);
if (effectTextBefore.length > 0) {
tex += "{" + effectTextBefore + "}";
}
tex += "." + note.duration;
let effectTextAfter = this.getEffectText(effectsAfter);
if (effectTextAfter.length > 0) {
tex += "{" + effectTextAfter + "}";
}
return tex;
}
}
}
module.exports = ConvertToTex;