ArticulationReader.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. import { Articulation } from "../../VoiceData/Articulation";
  9. import { Note } from "../../VoiceData/Note";
  10. import { EngravingRules } from "../../Graphical/EngravingRules";
  11. export class ArticulationReader {
  12. private rules: EngravingRules;
  13. constructor(rules: EngravingRules) {
  14. this.rules = rules;
  15. }
  16. private getAccEnumFromString(input: string): AccidentalEnum {
  17. switch (input) {
  18. case "sharp":
  19. return AccidentalEnum.SHARP;
  20. case "flat":
  21. return AccidentalEnum.FLAT;
  22. case "natural":
  23. return AccidentalEnum.NATURAL;
  24. case "double-sharp":
  25. case "sharp-sharp":
  26. return AccidentalEnum.DOUBLESHARP;
  27. case "double-flat":
  28. case "flat-flat":
  29. return AccidentalEnum.DOUBLEFLAT;
  30. case "triple-sharp":
  31. return AccidentalEnum.TRIPLESHARP;
  32. case "triple-flat":
  33. return AccidentalEnum.TRIPLEFLAT;
  34. case "quarter-sharp":
  35. return AccidentalEnum.QUARTERTONESHARP;
  36. case "quarter-flat":
  37. return AccidentalEnum.QUARTERTONEFLAT;
  38. case "three-quarters-sharp":
  39. return AccidentalEnum.THREEQUARTERSSHARP;
  40. case "three-quarters-flat":
  41. return AccidentalEnum.THREEQUARTERSFLAT;
  42. case "slash-quarter-sharp":
  43. return AccidentalEnum.SLASHQUARTERSHARP;
  44. case "slash-sharp":
  45. return AccidentalEnum.SLASHSHARP;
  46. case "double-slash-flat":
  47. return AccidentalEnum.DOUBLESLASHFLAT;
  48. case "sori":
  49. return AccidentalEnum.SORI;
  50. case "koron":
  51. return AccidentalEnum.KORON;
  52. default:
  53. return AccidentalEnum.NONE;
  54. }
  55. }
  56. /**
  57. * This method adds an Articulation Expression to the currentVoiceEntry.
  58. * @param node
  59. * @param currentVoiceEntry
  60. */
  61. public addArticulationExpression(node: IXmlElement, currentVoiceEntry: VoiceEntry): void {
  62. if (node !== undefined && node.elements().length > 0) {
  63. const childNodes: IXmlElement[] = node.elements();
  64. for (let idx: number = 0, len: number = childNodes.length; idx < len; ++idx) {
  65. const childNode: IXmlElement = childNodes[idx];
  66. let name: string = childNode.name;
  67. try {
  68. // some Articulations appear in Xml separated with a "-" (eg strong-accent), we remove it for enum parsing
  69. name = name.replace("-", "");
  70. const articulationEnum: ArticulationEnum = ArticulationEnum[name];
  71. if (VoiceEntry.isSupportedArticulation(articulationEnum)) {
  72. let placement: PlacementEnum = PlacementEnum.NotYetDefined;
  73. const placementValue: string = childNode.attribute("placement")?.value;
  74. if (placementValue === "above") {
  75. placement = PlacementEnum.Above;
  76. } else if (placementValue === "below") {
  77. placement = PlacementEnum.Below;
  78. }
  79. const newArticulation: Articulation = new Articulation(articulationEnum, placement);
  80. // staccato should be first // necessary?
  81. if (name === "staccato") {
  82. if (currentVoiceEntry.Articulations.length > 0 &&
  83. currentVoiceEntry.Articulations[0].articulationEnum !== ArticulationEnum.staccato) {
  84. currentVoiceEntry.Articulations.splice(0, 0, newArticulation); // TODO can't this overwrite another articulation?
  85. }
  86. }
  87. if (name === "strongaccent") { // see name.replace("-", "") above
  88. const marcatoType: string = childNode?.attribute("type")?.value;
  89. if (marcatoType === "up") {
  90. newArticulation.articulationEnum = ArticulationEnum.marcatoup;
  91. } else if (marcatoType === "down") {
  92. newArticulation.articulationEnum = ArticulationEnum.marcatodown;
  93. }
  94. }
  95. // don't add the same articulation twice
  96. if (!currentVoiceEntry.hasArticulation(newArticulation)) {
  97. currentVoiceEntry.Articulations.push(newArticulation);
  98. switch (newArticulation.articulationEnum) {
  99. case ArticulationEnum.staccato:
  100. case ArticulationEnum.staccatissimo:
  101. currentVoiceEntry.DurationModifier = newArticulation;
  102. break;
  103. case ArticulationEnum.accent:
  104. case ArticulationEnum.strongaccent:
  105. currentVoiceEntry.VolumeModifier = newArticulation;
  106. break;
  107. default:
  108. break;
  109. }
  110. }
  111. }
  112. } catch (ex) {
  113. const errorMsg: string = "Invalid note articulation.";
  114. log.debug("addArticulationExpression", errorMsg, ex);
  115. return;
  116. }
  117. }
  118. }
  119. }
  120. /**
  121. * This method add a Fermata to the currentVoiceEntry.
  122. * @param xmlNode
  123. * @param currentVoiceEntry
  124. */
  125. public addFermata(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
  126. // fermata appears as separate tag in XML
  127. let articulationEnum: ArticulationEnum = ArticulationEnum.fermata;
  128. if (xmlNode.attributes().length > 0 && xmlNode.attribute("type")) {
  129. if (xmlNode.attribute("type").value === "inverted") {
  130. articulationEnum = ArticulationEnum.invertedfermata;
  131. }
  132. }
  133. let placement: PlacementEnum = PlacementEnum.Above;
  134. if (xmlNode.attribute("placement")?.value === "below") {
  135. placement = PlacementEnum.Below;
  136. }
  137. // add to VoiceEntry
  138. currentVoiceEntry.Articulations.push(new Articulation(articulationEnum, placement));
  139. }
  140. /**
  141. * This method add a technical Articulation to the currentVoiceEntry.
  142. * @param technicalNode
  143. * @param currentVoiceEntry
  144. */
  145. public addTechnicalArticulations(technicalNode: IXmlElement, currentVoiceEntry: VoiceEntry, currentNote: Note): void {
  146. interface XMLElementToArticulationEnum {
  147. [xmlElement: string]: ArticulationEnum;
  148. }
  149. const xmlElementToArticulationEnum: XMLElementToArticulationEnum = {
  150. "bend": ArticulationEnum.bend,
  151. "down-bow": ArticulationEnum.downbow,
  152. "open-string": ArticulationEnum.naturalharmonic,
  153. "snap-pizzicato": ArticulationEnum.snappizzicato,
  154. "stopped": ArticulationEnum.lefthandpizzicato,
  155. "up-bow": ArticulationEnum.upbow,
  156. // fingering is special case
  157. };
  158. for (const xmlArticulation in xmlElementToArticulationEnum) {
  159. if (!xmlElementToArticulationEnum.hasOwnProperty(xmlArticulation)) {
  160. continue;
  161. }
  162. const articulationEnum: ArticulationEnum = xmlElementToArticulationEnum[xmlArticulation];
  163. const node: IXmlElement = technicalNode.element(xmlArticulation);
  164. if (node) {
  165. let placement: PlacementEnum; // set undefined by default, to not restrict placement
  166. if (node.attribute("placement")?.value === "above") {
  167. placement = PlacementEnum.Above;
  168. }
  169. if (node.attribute("placement")?.value === "below") {
  170. placement = PlacementEnum.Below;
  171. }
  172. const newArticulation: Articulation = new Articulation(articulationEnum, placement);
  173. if (!currentVoiceEntry.hasArticulation(newArticulation)) {
  174. currentVoiceEntry.Articulations.push(newArticulation);
  175. }
  176. }
  177. }
  178. const nodeFingering: IXmlElement = technicalNode.element("fingering");
  179. if (nodeFingering) {
  180. const currentTechnicalInstruction: TechnicalInstruction = this.createTechnicalInstruction(nodeFingering, currentNote);
  181. currentTechnicalInstruction.type = TechnicalInstructionType.Fingering;
  182. currentNote.Fingering = currentTechnicalInstruction;
  183. currentVoiceEntry.TechnicalInstructions.push(currentTechnicalInstruction);
  184. }
  185. const nodeString: IXmlElement = technicalNode.element("string");
  186. if (nodeString) {
  187. const currentTechnicalInstruction: TechnicalInstruction = this.createTechnicalInstruction(nodeString, currentNote);
  188. currentTechnicalInstruction.type = TechnicalInstructionType.String;
  189. currentNote.StringInstruction = currentTechnicalInstruction;
  190. currentVoiceEntry.TechnicalInstructions.push(currentTechnicalInstruction);
  191. }
  192. }
  193. private createTechnicalInstruction(stringOrFingeringNode: IXmlElement, note: Note): TechnicalInstruction {
  194. const technicalInstruction: TechnicalInstruction = new TechnicalInstruction();
  195. technicalInstruction.sourceNote = note;
  196. technicalInstruction.value = stringOrFingeringNode.value;
  197. const placement: Attr = stringOrFingeringNode.attribute("placement");
  198. if (this.rules.FingeringPositionFromXML) {
  199. technicalInstruction.placement = this.getPlacement(placement);
  200. }
  201. return technicalInstruction;
  202. }
  203. private getPlacement(placementAttr: Attr, defaultPlacement: PlacementEnum = PlacementEnum.NotYetDefined): PlacementEnum {
  204. if (defaultPlacement !== PlacementEnum.NotYetDefined) { // usually from EngravingRules
  205. return defaultPlacement;
  206. }
  207. if (placementAttr) {
  208. switch (placementAttr.value) {
  209. case "above":
  210. return PlacementEnum.Above;
  211. case "below":
  212. return PlacementEnum.Below;
  213. case "left": // not valid in MusicXML 3.1
  214. return PlacementEnum.Left;
  215. case "right": // not valid in MusicXML 3.1
  216. return PlacementEnum.Right;
  217. default:
  218. return PlacementEnum.NotYetDefined;
  219. }
  220. } else {
  221. return PlacementEnum.NotYetDefined;
  222. }
  223. }
  224. /**
  225. * This method adds an Ornament to the currentVoiceEntry.
  226. * @param ornamentsNode
  227. * @param currentVoiceEntry
  228. */
  229. public addOrnament(ornamentsNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
  230. if (ornamentsNode) {
  231. let ornament: OrnamentContainer = undefined;
  232. interface XMLElementToOrnamentEnum {
  233. [xmlElement: string]: OrnamentEnum;
  234. }
  235. const elementToOrnamentEnum: XMLElementToOrnamentEnum = {
  236. "delayed-inverted-turn": OrnamentEnum.DelayedInvertedTurn,
  237. "delayed-turn": OrnamentEnum.DelayedTurn,
  238. "inverted-mordent": OrnamentEnum.InvertedMordent,
  239. "inverted-turn": OrnamentEnum.InvertedTurn,
  240. "mordent": OrnamentEnum.Mordent,
  241. "trill-mark": OrnamentEnum.Trill,
  242. "turn": OrnamentEnum.Turn,
  243. // further ornaments are not yet supported by MusicXML (3.1).
  244. };
  245. for (const ornamentElement in elementToOrnamentEnum) {
  246. if (!elementToOrnamentEnum.hasOwnProperty(ornamentElement)) {
  247. continue;
  248. }
  249. const node: IXmlElement = ornamentsNode.element(ornamentElement);
  250. if (node) {
  251. ornament = new OrnamentContainer(elementToOrnamentEnum[ornamentElement]);
  252. const placementAttr: Attr = node.attribute("placement");
  253. if (placementAttr) {
  254. const placementString: string = placementAttr.value;
  255. if (placementString === "below") {
  256. ornament.placement = PlacementEnum.Below;
  257. }
  258. }
  259. }
  260. }
  261. if (ornament) {
  262. const accidentalsList: IXmlElement[] = ornamentsNode.elements("accidental-mark");
  263. if (accidentalsList) {
  264. let placement: PlacementEnum = PlacementEnum.Below;
  265. let accidental: AccidentalEnum = AccidentalEnum.NONE;
  266. const accidentalsListArr: IXmlElement[] = accidentalsList;
  267. for (let idx: number = 0, len: number = accidentalsListArr.length; idx < len; ++idx) {
  268. const accidentalNode: IXmlElement = accidentalsListArr[idx];
  269. let text: string = accidentalNode.value;
  270. accidental = this.getAccEnumFromString(text);
  271. const placementAttr: IXmlAttribute = accidentalNode.attribute("placement");
  272. if (accidentalNode.hasAttributes && placementAttr) {
  273. text = placementAttr.value;
  274. if (text === "above") {
  275. placement = PlacementEnum.Above;
  276. } else if (text === "below") {
  277. placement = PlacementEnum.Below;
  278. }
  279. }
  280. if (placement === PlacementEnum.Above) {
  281. ornament.AccidentalAbove = accidental;
  282. } else if (placement === PlacementEnum.Below) {
  283. ornament.AccidentalBelow = accidental;
  284. }
  285. }
  286. }
  287. // add this to currentVoiceEntry
  288. currentVoiceEntry.OrnamentContainer = ornament;
  289. }
  290. }
  291. } // /addOrnament
  292. }