ArticulationReader.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import {ArticulationEnum, VoiceEntry} from "../../VoiceData/VoiceEntry";
  2. import {IXmlAttribute, IXmlElement} from "../../../Common/FileIO/Xml";
  3. import log from "loglevel";
  4. import {TechnicalInstruction, TechnicalInstructionType} from "../../VoiceData/Instructions/TechnicalInstruction";
  5. import {OrnamentContainer, OrnamentEnum} from "../../VoiceData/OrnamentContainer";
  6. import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
  7. import {AccidentalEnum} from "../../../Common/DataObjects/Pitch";
  8. export class ArticulationReader {
  9. private getAccEnumFromString(input: string): AccidentalEnum {
  10. switch (input) {
  11. case "sharp":
  12. return AccidentalEnum.SHARP;
  13. case "flat":
  14. return AccidentalEnum.FLAT;
  15. case "natural":
  16. return AccidentalEnum.NATURAL;
  17. case "double-sharp":
  18. case "sharp-sharp":
  19. return AccidentalEnum.DOUBLESHARP;
  20. case "double-flat":
  21. case "flat-flat":
  22. return AccidentalEnum.DOUBLEFLAT;
  23. case "quarter-sharp":
  24. return AccidentalEnum.QUARTERTONESHARP;
  25. case "quarter-flat":
  26. return AccidentalEnum.QUARTERTONEFLAT;
  27. case "triple-sharp":
  28. return AccidentalEnum.TRIPLESHARP;
  29. case "triple-flat":
  30. return AccidentalEnum.TRIPLEFLAT;
  31. default:
  32. return AccidentalEnum.NONE;
  33. }
  34. }
  35. /**
  36. * This method adds an Articulation Expression to the currentVoiceEntry.
  37. * @param node
  38. * @param currentVoiceEntry
  39. */
  40. public addArticulationExpression(node: IXmlElement, currentVoiceEntry: VoiceEntry): void {
  41. if (node !== undefined && node.elements().length > 0) {
  42. const childNodes: IXmlElement[] = node.elements();
  43. for (let idx: number = 0, len: number = childNodes.length; idx < len; ++idx) {
  44. const childNode: IXmlElement = childNodes[idx];
  45. let name: string = childNode.name;
  46. try {
  47. // some Articulations appear in Xml separated with a "-" (eg strong-accent), we remove it for enum parsing
  48. name = name.replace("-", "");
  49. let articulationEnum: ArticulationEnum = ArticulationEnum[name];
  50. if (VoiceEntry.isSupportedArticulation(articulationEnum)) {
  51. // staccato should be first // necessary?
  52. if (name === "staccato") {
  53. if (currentVoiceEntry.Articulations.length > 0 &&
  54. currentVoiceEntry.Articulations[0] !== ArticulationEnum.staccato) {
  55. currentVoiceEntry.Articulations.splice(0, 0, articulationEnum); // TODO can't this overwrite another articulation?
  56. }
  57. }
  58. if (name === "strongaccent") { // see name.replace("-", "") above
  59. const marcatoType: string = childNode?.attribute("type")?.value;
  60. if (marcatoType === "up") {
  61. articulationEnum = ArticulationEnum.marcatoup;
  62. } else if (marcatoType === "down") {
  63. articulationEnum = ArticulationEnum.marcatodown;
  64. }
  65. }
  66. // don't add the same articulation twice
  67. if (currentVoiceEntry.Articulations.indexOf(articulationEnum) === -1) {
  68. currentVoiceEntry.Articulations.push(articulationEnum);
  69. }
  70. }
  71. } catch (ex) {
  72. const errorMsg: string = "Invalid note articulation.";
  73. log.debug("addArticulationExpression", errorMsg, ex);
  74. return;
  75. }
  76. }
  77. }
  78. }
  79. /**
  80. * This method add a Fermata to the currentVoiceEntry.
  81. * @param xmlNode
  82. * @param currentVoiceEntry
  83. */
  84. public addFermata(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
  85. // fermata appears as separate tag in XML
  86. let articulationEnum: ArticulationEnum = ArticulationEnum.fermata;
  87. if (xmlNode.attributes().length > 0 && xmlNode.attribute("type")) {
  88. if (xmlNode.attribute("type").value === "inverted") {
  89. articulationEnum = ArticulationEnum.invertedfermata;
  90. }
  91. }
  92. // add to VoiceEntry
  93. currentVoiceEntry.Articulations.push(articulationEnum);
  94. }
  95. /**
  96. * This method add a technical Articulation to the currentVoiceEntry.
  97. * @param xmlNode
  98. * @param currentVoiceEntry
  99. */
  100. public addTechnicalArticulations(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
  101. interface XMLElementToArticulationEnum {
  102. [xmlElement: string]: ArticulationEnum;
  103. }
  104. const xmlElementToArticulationEnum: XMLElementToArticulationEnum = {
  105. "bend": ArticulationEnum.bend,
  106. "down-bow": ArticulationEnum.downbow,
  107. "open-string": ArticulationEnum.naturalharmonic,
  108. "snap-pizzicato": ArticulationEnum.snappizzicato,
  109. "stopped": ArticulationEnum.lefthandpizzicato,
  110. "up-bow": ArticulationEnum.upbow,
  111. // fingering is special case
  112. };
  113. for (const xmlArticulation in xmlElementToArticulationEnum) {
  114. if (!xmlElementToArticulationEnum.hasOwnProperty(xmlArticulation)) {
  115. continue;
  116. }
  117. const articulationEnum: ArticulationEnum = xmlElementToArticulationEnum[xmlArticulation];
  118. const node: IXmlElement = xmlNode.element(xmlArticulation);
  119. if (node) {
  120. if (currentVoiceEntry.Articulations.indexOf(articulationEnum) === -1) {
  121. currentVoiceEntry.Articulations.push(articulationEnum);
  122. }
  123. }
  124. }
  125. const nodeFingering: IXmlElement = xmlNode.element("fingering");
  126. if (nodeFingering) {
  127. const currentTechnicalInstruction: TechnicalInstruction = new TechnicalInstruction();
  128. currentTechnicalInstruction.type = TechnicalInstructionType.Fingering;
  129. currentTechnicalInstruction.value = nodeFingering.value;
  130. currentTechnicalInstruction.placement = PlacementEnum.NotYetDefined;
  131. const placement: Attr = nodeFingering.attribute("placement");
  132. if (placement !== undefined && placement !== null) {
  133. switch (placement.value) {
  134. case "above":
  135. currentTechnicalInstruction.placement = PlacementEnum.Above;
  136. break;
  137. case "below":
  138. currentTechnicalInstruction.placement = PlacementEnum.Below;
  139. break;
  140. case "left": // not valid in MusicXML 3.1
  141. currentTechnicalInstruction.placement = PlacementEnum.Left;
  142. break;
  143. case "right": // not valid in MusicXML 3.1
  144. currentTechnicalInstruction.placement = PlacementEnum.Right;
  145. break;
  146. default:
  147. currentTechnicalInstruction.placement = PlacementEnum.NotYetDefined;
  148. }
  149. }
  150. currentVoiceEntry.TechnicalInstructions.push(currentTechnicalInstruction);
  151. }
  152. }
  153. /**
  154. * This method adds an Ornament to the currentVoiceEntry.
  155. * @param ornamentsNode
  156. * @param currentVoiceEntry
  157. */
  158. public addOrnament(ornamentsNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
  159. if (ornamentsNode) {
  160. let ornament: OrnamentContainer = undefined;
  161. interface XMLElementToOrnamentEnum {
  162. [xmlElement: string]: OrnamentEnum;
  163. }
  164. const elementToOrnamentEnum: XMLElementToOrnamentEnum = {
  165. "delayed-inverted-turn": OrnamentEnum.DelayedInvertedTurn,
  166. "delayed-turn": OrnamentEnum.DelayedTurn,
  167. "inverted-mordent": OrnamentEnum.InvertedMordent,
  168. "inverted-turn": OrnamentEnum.InvertedTurn,
  169. "mordent": OrnamentEnum.Mordent,
  170. "trill-mark": OrnamentEnum.Trill,
  171. "turn": OrnamentEnum.Turn,
  172. // further ornaments are not yet supported by MusicXML (3.1).
  173. };
  174. for (const ornamentElement in elementToOrnamentEnum) {
  175. if (!elementToOrnamentEnum.hasOwnProperty(ornamentElement)) {
  176. continue;
  177. }
  178. const node: IXmlElement = ornamentsNode.element(ornamentElement);
  179. if (node) {
  180. ornament = new OrnamentContainer(elementToOrnamentEnum[ornamentElement]);
  181. }
  182. }
  183. if (ornament) {
  184. const accidentalsList: IXmlElement[] = ornamentsNode.elements("accidental-mark");
  185. if (accidentalsList) {
  186. let placement: PlacementEnum = PlacementEnum.Below;
  187. let accidental: AccidentalEnum = AccidentalEnum.NONE;
  188. const accidentalsListArr: IXmlElement[] = accidentalsList;
  189. for (let idx: number = 0, len: number = accidentalsListArr.length; idx < len; ++idx) {
  190. const accidentalNode: IXmlElement = accidentalsListArr[idx];
  191. let text: string = accidentalNode.value;
  192. accidental = this.getAccEnumFromString(text);
  193. const placementAttr: IXmlAttribute = accidentalNode.attribute("placement");
  194. if (accidentalNode.hasAttributes && placementAttr) {
  195. text = placementAttr.value;
  196. if (text === "above") {
  197. placement = PlacementEnum.Above;
  198. } else if (text === "below") {
  199. placement = PlacementEnum.Below;
  200. }
  201. }
  202. if (placement === PlacementEnum.Above) {
  203. ornament.AccidentalAbove = accidental;
  204. } else if (placement === PlacementEnum.Below) {
  205. ornament.AccidentalBelow = accidental;
  206. }
  207. }
  208. }
  209. // add this to currentVoiceEntry
  210. currentVoiceEntry.OrnamentContainer = ornament;
  211. }
  212. }
  213. } // /addOrnament
  214. }