VexflowStafflineNoteCalculator.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { IStafflineNoteCalculator } from "../../Interfaces/IStafflineNoteCalculator";
  2. import { GraphicalNote } from "../GraphicalNote";
  3. import { Pitch, NoteEnum } from "../../../Common/DataObjects/Pitch";
  4. import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
  5. import { Dictionary } from "typescript-collections";
  6. import { EngravingRules } from "../EngravingRules";
  7. import { ClefEnum } from "../../VoiceData/Instructions/ClefInstruction";
  8. import { StemDirectionType, VoiceEntry } from "../../VoiceData/VoiceEntry";
  9. export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator {
  10. private rules: EngravingRules;
  11. private staffPitchListMapping: Dictionary<number, Array<Pitch>> = new Dictionary<number, Array<Pitch>>();
  12. //These render on the single line by default
  13. private baseLineNote: NoteEnum = NoteEnum.B;
  14. private baseLineOctave: number = 1;
  15. constructor(rules: EngravingRules) {
  16. this.rules = rules;
  17. }
  18. /**
  19. * This method is called for each note during the calc phase. We want to track all possible positions to make decisions
  20. * during layout about where notes should be positioned.
  21. * This directly puts notes that share a line to the same position, regardless of voice
  22. * @param graphicalNote The note to be checked/positioned
  23. * @param staffIndex The staffline the note is on
  24. */
  25. public trackNote(graphicalNote: GraphicalNote): void {
  26. if (!(graphicalNote instanceof VexFlowGraphicalNote) || graphicalNote.Clef().ClefType !== ClefEnum.percussion ||
  27. graphicalNote.sourceNote.isRest() || this.rules.PercussionOneLineCutoff === 0 ||
  28. this.rules.PercussionForceVoicesOneLineCutoff === -1) {
  29. return;
  30. }
  31. const staffIndex: number =
  32. graphicalNote.parentVoiceEntry.parentStaffEntry.sourceStaffEntry.ParentStaff.idInMusicSheet;
  33. let currentPitchList: Array<Pitch> = undefined;
  34. if (!this.staffPitchListMapping.containsKey(staffIndex)) {
  35. this.staffPitchListMapping.setValue(staffIndex, new Array<Pitch>());
  36. }
  37. currentPitchList = this.staffPitchListMapping.getValue(staffIndex);
  38. const pitch: Pitch = graphicalNote.sourceNote.Pitch;
  39. VexflowStafflineNoteCalculator.findOrInsert(currentPitchList, pitch);
  40. }
  41. private static PitchIndexOf(array: Array<Pitch>, pitch: Pitch, start: number = 0): number {
  42. if (start > array.length - 1) {
  43. return -1;
  44. }
  45. for (let i: number = start; i < array.length; i++) {
  46. const p2: Pitch = array[i];
  47. if (pitch.OperatorEquals(p2)) {
  48. return i;
  49. }
  50. }
  51. return -1;
  52. }
  53. private static findOrInsert(array: Array<Pitch>, pitch: Pitch): number {
  54. for (let i: number = 0; i < array.length; i++) {
  55. const p2: Pitch = array[i];
  56. if (pitch.OperatorEquals(p2)) {
  57. return i;
  58. } else {
  59. if (pitch.OperatorFundamentalLessThan(p2)) {
  60. array.splice(i, 0, pitch);
  61. return i;
  62. }
  63. }
  64. }
  65. //If we reach here, we've reached the end of the array.
  66. //Means its the greatest pitch
  67. array.push(pitch);
  68. return array.length - 1;
  69. }
  70. /**
  71. * This method is called for each note, and should make any necessary position changes based on the number of stafflines, clef, etc.
  72. * @param graphicalNote The note to be checked/positioned
  73. * @param staffIndex The staffline that this note exists on
  74. * @returns the newly positioned note
  75. */
  76. public positionNote(graphicalNote: GraphicalNote): GraphicalNote {
  77. const staffIndex: number =
  78. graphicalNote.parentVoiceEntry.parentStaffEntry.sourceStaffEntry.ParentStaff.idInMusicSheet;
  79. if (!(graphicalNote instanceof VexFlowGraphicalNote) || graphicalNote.sourceNote.isRest()
  80. || !this.staffPitchListMapping.containsKey(staffIndex)) {
  81. return graphicalNote;
  82. }
  83. const currentPitchList: Array<Pitch> = this.staffPitchListMapping.getValue(staffIndex);
  84. //const xmlSingleStaffline: boolean = graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaff.StafflineCount === 1;
  85. const positionByXml: boolean = this.rules.PercussionUseXMLDisplayStep &&
  86. graphicalNote.sourceNote.displayStepUnpitched !== undefined;
  87. if (currentPitchList.length > this.rules.PercussionOneLineCutoff && !positionByXml) {
  88. //Don't need to position notes. We aren't under the cutoff
  89. return graphicalNote;
  90. }
  91. const vfGraphicalNote: VexFlowGraphicalNote = graphicalNote as VexFlowGraphicalNote;
  92. const notePitch: Pitch = graphicalNote.sourceNote.Pitch;
  93. let displayNote: NoteEnum = this.baseLineNote;
  94. let displayOctave: number = this.baseLineOctave;
  95. if (this.rules.PercussionUseXMLDisplayStep
  96. && graphicalNote.sourceNote.displayStepUnpitched !== undefined) {
  97. //&& xmlSingleStaffline) {
  98. displayNote = graphicalNote.sourceNote.displayStepUnpitched;
  99. displayOctave = graphicalNote.sourceNote.displayOctaveUnpitched + this.rules.PercussionOneLineXMLDisplayStepOctaveOffset;
  100. }
  101. //If we only need to render on one line
  102. if (currentPitchList.length <= this.rules.PercussionForceVoicesOneLineCutoff) {
  103. vfGraphicalNote.setAccidental(new Pitch(displayNote, displayOctave, notePitch.Accidental));
  104. } else {
  105. const pitchIndex: number = VexflowStafflineNoteCalculator.PitchIndexOf(currentPitchList, notePitch);
  106. if (pitchIndex > -1) {
  107. const half: number = Math.ceil(currentPitchList.length / 2);
  108. if (!this.rules.PercussionUseXMLDisplayStep) {
  109. if (pitchIndex >= half) {
  110. //position above
  111. displayOctave = 2;
  112. switch ((pitchIndex - half) % 5) {
  113. case 1:
  114. displayNote = NoteEnum.E;
  115. break;
  116. case 2:
  117. displayNote = NoteEnum.G;
  118. break;
  119. case 3:
  120. displayNote = NoteEnum.B;
  121. break;
  122. case 4:
  123. displayNote = NoteEnum.D;
  124. displayOctave = 3;
  125. break;
  126. default:
  127. displayNote = NoteEnum.C;
  128. break;
  129. }
  130. } else { //position below
  131. switch (pitchIndex % 5) {
  132. case 1:
  133. displayNote = NoteEnum.F;
  134. break;
  135. case 2:
  136. displayNote = NoteEnum.D;
  137. break;
  138. case 3:
  139. displayNote = NoteEnum.B;
  140. displayOctave = 0;
  141. break;
  142. case 4:
  143. displayNote = NoteEnum.G;
  144. displayOctave = 0;
  145. break;
  146. default:
  147. displayNote = NoteEnum.A;
  148. break;
  149. }
  150. }
  151. }
  152. const mappedPitch: Pitch = new Pitch(displayNote, displayOctave, notePitch.Accidental);
  153. //Map the pitch, set stems properly
  154. vfGraphicalNote.setAccidental(mappedPitch);
  155. const parentVoiceEntry: VoiceEntry = vfGraphicalNote.parentVoiceEntry.parentVoiceEntry;
  156. // Only switch stems if we aren't sharing stems with another note
  157. if (!this.rules.SetWantedStemDirectionByXml && parentVoiceEntry.Notes.length < 2) {
  158. if (mappedPitch.Octave > this.baseLineOctave ||
  159. (mappedPitch.FundamentalNote === this.baseLineNote && mappedPitch.Octave === this.baseLineOctave)) {
  160. vfGraphicalNote.parentVoiceEntry.parentVoiceEntry.WantedStemDirection = StemDirectionType.Up;
  161. } else {
  162. vfGraphicalNote.parentVoiceEntry.parentVoiceEntry.WantedStemDirection = StemDirectionType.Down;
  163. }
  164. }
  165. }
  166. }
  167. return vfGraphicalNote;
  168. }
  169. /**
  170. * Get the number of unique "voices" or note positions
  171. * @param staffIndex The Staffline to get the count of
  172. */
  173. public getStafflineUniquePositionCount(staffIndex: number): number {
  174. if (this.staffPitchListMapping.containsKey(staffIndex)) {
  175. return this.staffPitchListMapping.getValue(staffIndex).length;
  176. }
  177. return 0;
  178. }
  179. }