AlignmentManager.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import { StaffLine } from "../StaffLine";
  2. import { BoundingBox } from "../BoundingBox";
  3. import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression";
  4. import { AbstractGraphicalExpression } from "../AbstractGraphicalExpression";
  5. import { PointF2D } from "../../../Common/DataObjects/PointF2D";
  6. import { EngravingRules } from "../EngravingRules";
  7. import { PlacementEnum } from "../../VoiceData/Expressions";
  8. import { GraphicalUnknownExpression } from "../GraphicalUnknownExpression";
  9. export class AlignmentManager {
  10. private parentStaffline: StaffLine;
  11. private rules: EngravingRules;
  12. constructor(staffline: StaffLine) {
  13. this.parentStaffline = staffline;
  14. this.rules = this.parentStaffline.ParentMusicSystem.rules;
  15. }
  16. public alignDynamicExpressions(): void {
  17. // Find close expressions along the staffline. Group them into tuples
  18. const groups: AbstractGraphicalExpression[][] = [];
  19. let tmpList: AbstractGraphicalExpression[] = new Array<AbstractGraphicalExpression>();
  20. for (let aeIdx: number = 0; aeIdx < this.parentStaffline.AbstractExpressions.length - 1; aeIdx++) {
  21. const currentExpression: AbstractGraphicalExpression = this.parentStaffline.AbstractExpressions[aeIdx];
  22. const nextExpression: AbstractGraphicalExpression = this.parentStaffline.AbstractExpressions[aeIdx + 1];
  23. let currentExpressionPlacement: PlacementEnum = undefined;
  24. if (currentExpression?.SourceExpression) {
  25. currentExpressionPlacement = currentExpression.Placement;
  26. } else if (currentExpression instanceof GraphicalUnknownExpression) {
  27. currentExpressionPlacement = (currentExpression as GraphicalUnknownExpression).
  28. sourceMultiExpression?.getPlacementOfFirstEntry();
  29. }
  30. // same for nextExpression:
  31. let nextExpressionPlacement: PlacementEnum = undefined;
  32. if (nextExpression?.SourceExpression) {
  33. nextExpressionPlacement = nextExpression.Placement;
  34. } else if (nextExpression instanceof GraphicalUnknownExpression) {
  35. nextExpressionPlacement = (nextExpression as GraphicalUnknownExpression).
  36. sourceMultiExpression?.getPlacementOfFirstEntry();
  37. }
  38. // if (currentExpression?.SourceExpression === undefined ||
  39. // nextExpression?.SourceExpression === undefined) {
  40. // continue;
  41. // // TODO: this doesn't work yet for GraphicalUnknownExpression, because it doesn't have an AbstractExpression,
  42. // // so it doesn't have a .Placement.
  43. // // this lead to if (currentExpression.Placement...) crashing.
  44. // // same result:
  45. // // if (currentExpression instanceof GraphicalUnknownExpression ||
  46. // // nextExpression instanceof GraphicalUnknownExpression) {
  47. // // continue;
  48. // // }
  49. // } else {
  50. // samePlacement = currentExpression.Placement === nextExpression.Placement;
  51. // }
  52. // TODO this shifts dynamics in An die Ferne Geliebte, showing that there's something wrong with the RelativePositions etc with wedges
  53. // if (currentExpression instanceof GraphicalContinuousDynamicExpression) {
  54. // currentExpression.calcPsi();
  55. // }
  56. // if (nextExpression instanceof GraphicalContinuousDynamicExpression) {
  57. // nextExpression.calcPsi();
  58. // }
  59. if (currentExpressionPlacement === nextExpressionPlacement) {
  60. // if ((currentExpression as any).label?.label?.text?.startsWith("dim") ||
  61. // (nextExpression as any).label?.label?.text?.startsWith("dim")) {
  62. // console.log("here");
  63. // }
  64. const dist: PointF2D = this.getDistance(currentExpression.PositionAndShape, nextExpression.PositionAndShape);
  65. if (Math.abs(dist.x) < this.rules.DynamicExpressionMaxDistance) {
  66. // Prevent last found expression to be added twice. e.g. p<f as three close expressions
  67. if (tmpList.indexOf(currentExpression) === -1) {
  68. tmpList.push(currentExpression);
  69. }
  70. tmpList.push(nextExpression);
  71. } else {
  72. groups.push(tmpList);
  73. tmpList = new Array<AbstractGraphicalExpression>();
  74. }
  75. }
  76. }
  77. // If expressions are colliding at end, we need to add them too
  78. groups.push(tmpList);
  79. for (const aes of groups) {
  80. if (aes.length > 0) {
  81. // Get the median y position and shift all group members to that position
  82. const centerYs: number[] = aes.map(expr => expr.PositionAndShape.Center.y);
  83. // TODO this may not give the right position for wedges (GraphicalContinuousDynamic, !isVerbal())
  84. const yIdeal: number = Math.max(...centerYs);
  85. // for (const ae of aes) { // debug
  86. // if (ae.PositionAndShape.Center.y > 6) {
  87. // // dynamic positioned at edge of skybottomline
  88. // console.log(`max expression in measure ${ae.SourceExpression.parentMeasure.MeasureNumber}: `);
  89. // console.dir(aes);
  90. // }
  91. // }
  92. for (let exprIdx: number = 0; exprIdx < aes.length; exprIdx++) {
  93. const expr: AbstractGraphicalExpression = aes[exprIdx];
  94. const centerOffset: number = centerYs[exprIdx] - yIdeal;
  95. // TODO centerOffset is way too big sometimes, like 7.0 in An die Ferne Geliebte (measure 10, dim.)
  96. // FIXME: Expressions should not behave differently.
  97. if (expr instanceof VexFlowContinuousDynamicExpression) {
  98. (expr as VexFlowContinuousDynamicExpression).shiftYPosition(-centerOffset);
  99. (expr as VexFlowContinuousDynamicExpression).calcPsi();
  100. } else {
  101. // TODO: The 0.8 are because the letters are a bit too far done
  102. expr.PositionAndShape.RelativePosition.y -= centerOffset * 0.8;
  103. // note: verbal GraphicalContinuousDynamicExpressions have a label, nonverbal ones don't.
  104. // take care to update and take the right bounding box for skyline.
  105. expr.PositionAndShape.calculateBoundingBox();
  106. }
  107. // Squeeze wedges
  108. if ((expr as VexFlowContinuousDynamicExpression).squeeze) {
  109. const nextExpression: AbstractGraphicalExpression = exprIdx < aes.length - 1 ? aes[exprIdx + 1] : undefined;
  110. const prevExpression: AbstractGraphicalExpression = exprIdx > 0 ? aes[exprIdx - 1] : undefined;
  111. if (nextExpression) {
  112. const overlapRight: PointF2D = this.getOverlap(expr.PositionAndShape, nextExpression.PositionAndShape);
  113. (expr as VexFlowContinuousDynamicExpression).squeeze(-(overlapRight.x + this.rules.DynamicExpressionSpacer));
  114. }
  115. if (prevExpression) {
  116. const overlapLeft: PointF2D = this.getOverlap(prevExpression.PositionAndShape, expr.PositionAndShape);
  117. (expr as VexFlowContinuousDynamicExpression).squeeze(overlapLeft.x + this.rules.DynamicExpressionSpacer);
  118. }
  119. }
  120. }
  121. }
  122. }
  123. }
  124. /**
  125. * Get distance between two bounding boxes
  126. * @param a First bounding box
  127. * @param b Second bounding box
  128. */
  129. private getDistance(a: BoundingBox, b: BoundingBox): PointF2D {
  130. const rightBorderA: number = a.RelativePosition.x + a.BorderMarginRight;
  131. const leftBorderB: number = b.RelativePosition.x + b.BorderMarginLeft;
  132. const bottomBorderA: number = a.RelativePosition.y + a.BorderMarginBottom;
  133. const topBorderB: number = b.RelativePosition.y + b.BorderMarginTop;
  134. return new PointF2D(leftBorderB - rightBorderA,
  135. topBorderB - bottomBorderA);
  136. // note: this is a distance vector, not absolute distance, otherwise we need Math.abs
  137. }
  138. /**
  139. * Get overlap of two bounding boxes
  140. * @param a First bounding box
  141. * @param b Second bounding box
  142. */
  143. private getOverlap(a: BoundingBox, b: BoundingBox): PointF2D {
  144. return new PointF2D((a.RelativePosition.x + a.BorderMarginRight) - (b.RelativePosition.x + b.BorderMarginLeft),
  145. (a.RelativePosition.y + a.BorderMarginBottom) - (b.RelativePosition.y + b.BorderMarginTop));
  146. }
  147. }