ArticulationReader.ts 7.5 KB

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