GraphicalContinuousDynamicExpression.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. import { GraphicalLine } from "./GraphicalLine";
  2. import { StaffLine } from "./StaffLine";
  3. import { GraphicalMeasure } from "./GraphicalMeasure";
  4. import { ContDynamicEnum, ContinuousDynamicExpression } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
  5. import { PointF2D } from "../../Common/DataObjects/PointF2D";
  6. import { AbstractGraphicalExpression } from "./AbstractGraphicalExpression";
  7. import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
  8. import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator";
  9. import { ISqueezable } from "./ISqueezable";
  10. import * as log from "loglevel";
  11. /**
  12. * This class prepares the graphical elements for a continuous expression. It calculates the wedges and
  13. * wrappings if they are split over system breaks.
  14. */
  15. export class GraphicalContinuousDynamicExpression extends AbstractGraphicalExpression implements ISqueezable {
  16. /** True if expression is split over system borders */
  17. private isSplittedPart: boolean;
  18. /** True if this expression should not be removed if re-rendered */
  19. private notToBeRemoved: boolean;
  20. /** Holds the line objects that can be drawn via implementation */
  21. private lines: GraphicalLine[] = [];
  22. private startMeasure: GraphicalMeasure;
  23. private endMeasure: GraphicalMeasure;
  24. /**
  25. * Create a new instance of the GraphicalContinuousDynamicExpression
  26. * @param continuousDynamic The continuous dynamic instruction read via ExpressionReader
  27. * @param staffLine The staffline where the exoression is attached
  28. */
  29. constructor(continuousDynamic: ContinuousDynamicExpression, staffLine: StaffLine) {
  30. super(staffLine, continuousDynamic);
  31. this.isSplittedPart = false;
  32. this.notToBeRemoved = false;
  33. }
  34. //#region Getter / Setter
  35. /** The graphical measure where the parent continuous dynamic expression starts */
  36. public get StartMeasure(): GraphicalMeasure { return this.startMeasure; }
  37. public set StartMeasure(value: GraphicalMeasure) { this.startMeasure = value; }
  38. /** The graphical measure where the parent continuous dynamic expression ends */
  39. public get EndMeasure(): GraphicalMeasure { return this.endMeasure; }
  40. public set EndMeasure(value: GraphicalMeasure) { this.endMeasure = value; }
  41. /** The staff lin where the graphical dynamic expressions ends */
  42. public get EndStaffLine(): StaffLine { return this.endMeasure ? this.endMeasure.ParentStaffLine : undefined; }
  43. /** Is true if this continuous expression is a wedge, that reaches over a system border and needs to be split into two. */
  44. public get IsSplittedPart(): boolean { return this.isSplittedPart; }
  45. public set IsSplittedPart(value: boolean) { this.isSplittedPart = value; }
  46. /** Is true if the dynamic is not a symbol but a text instruction. E.g. "decrescendo" */
  47. public get IsVerbal(): boolean { return this.ContinuousDynamic.Label !== undefined && this.ContinuousDynamic.Label.length > 0; }
  48. /** True if this expression should not be removed if re-rendered */
  49. public get NotToBeRemoved(): boolean { return this.notToBeRemoved; }
  50. public set NotToBeRemoved(value: boolean) { this.notToBeRemoved = value; }
  51. /** Holds the line objects that can be drawn via implementation */
  52. public get Lines(): GraphicalLine[] { return this.lines; }
  53. public get ContinuousDynamic(): ContinuousDynamicExpression { return this.SourceExpression as ContinuousDynamicExpression; }
  54. //#endregion
  55. //#region Public methods
  56. public updateSkyBottomLine(): void {
  57. // update Sky-BottomLine
  58. const skyBottomLineCalculator: SkyBottomLineCalculator = this.parentStaffLine.SkyBottomLineCalculator;
  59. const left: number = this.IsVerbal ? this.label.PositionAndShape.RelativePosition.x + this.label.PositionAndShape.BorderMarginLeft : 0;
  60. const right: number = this.IsVerbal ? this.label.PositionAndShape.RelativePosition.x + this.label.PositionAndShape.BorderMarginRight : 0;
  61. if (!this.IsVerbal && this.lines.length < 2) {
  62. log.warn("Not enough lines for SkyBottomLine calculation");
  63. }
  64. switch (this.Placement) {
  65. case PlacementEnum.Above:
  66. if (!this.IsVerbal) {
  67. skyBottomLineCalculator.updateSkyLineWithWedge(this.lines[0].Start, this.lines[0].End);
  68. } else {
  69. const yValue: number = this.label.PositionAndShape.BorderMarginTop + this.label.PositionAndShape.RelativePosition.y;
  70. skyBottomLineCalculator.updateSkyLineInRange(left, right, yValue);
  71. }
  72. break;
  73. case PlacementEnum.Below:
  74. if (!this.IsVerbal) {
  75. skyBottomLineCalculator.updateBottomLineWithWedge(this.lines[1].Start, this.lines[1].End);
  76. } else {
  77. const yValue: number = this.label.PositionAndShape.BorderMarginBottom + this.label.PositionAndShape.RelativePosition.y;
  78. skyBottomLineCalculator.updateBottomLineInRange(left, right, yValue);
  79. }
  80. break;
  81. default:
  82. log.error("Placement for GraphicalContinuousDynamicExpression is unknown");
  83. }
  84. }
  85. /**
  86. * Calculate crescendo lines for (full).
  87. * @param startX left most starting point
  88. * @param endX right mist ending point
  89. * @param y y placement
  90. * @param wedgeOpeningLength length of the opening
  91. * @param wedgeLineWidth line width of the wedge
  92. */
  93. public createCrescendoLines(startX: number, endX: number, y: number,
  94. wedgeOpeningLength: number = this.rules.WedgeOpeningLength, wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  95. const lineStart: PointF2D = new PointF2D(startX, y);
  96. const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeOpeningLength / 2);
  97. const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeOpeningLength / 2);
  98. this.addWedgeLines(lineStart, upperLineEnd, lowerLineEnd, wedgeLineWidth);
  99. }
  100. /**
  101. * Calculate crescendo lines for system break (first part).
  102. * @param startX left most starting point
  103. * @param endX right mist ending point
  104. * @param y y placement
  105. * @param wedgeMeasureEndOpeningLength length of opening at measure end
  106. * @param wedgeOpeningLength length of the opening
  107. * @param wedgeLineWidth line width of the wedge
  108. */
  109. public createFirstHalfCrescendoLines(startX: number, endX: number, y: number,
  110. wedgeMeasureEndOpeningLength: number = this.rules.WedgeMeasureEndOpeningLength,
  111. wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  112. const lineStart: PointF2D = new PointF2D(startX, y);
  113. const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeMeasureEndOpeningLength / 2);
  114. const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeMeasureEndOpeningLength / 2);
  115. this.addWedgeLines(lineStart, upperLineEnd, lowerLineEnd, wedgeLineWidth);
  116. }
  117. /**
  118. * Calculate crescendo lines for system break (second part).
  119. * @param startX left most starting point
  120. * @param endX right mist ending point
  121. * @param y y placement
  122. * @param wedgeMeasureBeginOpeningLength length of opening at measure start
  123. * @param wedgeOpeningLength length of the opening
  124. * @param wedgeLineWidth line width of the wedge
  125. */
  126. public createSecondHalfCrescendoLines(startX: number, endX: number, y: number,
  127. wedgeMeasureBeginOpeningLength: number = this.rules.WedgeMeasureBeginOpeningLength,
  128. wedgeOpeningLength: number = this.rules.WedgeOpeningLength,
  129. wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  130. const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeMeasureBeginOpeningLength / 2);
  131. const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeMeasureBeginOpeningLength / 2);
  132. const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeOpeningLength / 2);
  133. const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeOpeningLength / 2);
  134. this.addDoubleLines(upperLineStart, upperLineEnd, lowerLineStart, lowerLineEnd, wedgeLineWidth);
  135. }
  136. /**
  137. * This method recalculates the Crescendo Lines (for all cases).
  138. * @param startX left most starting point
  139. * @param endX right most ending point
  140. * @param y y placement
  141. */
  142. public recalculateCrescendoLines(startX: number, endX: number, y: number): void {
  143. const isSecondHalfSplit: boolean = Math.abs(this.lines[0].Start.y - this.lines[1].Start.y) > 0.0001;
  144. this.lines.clear();
  145. if (isSecondHalfSplit) {
  146. this.createSecondHalfCrescendoLines(startX, endX, y);
  147. } else if (this.isSplittedPart) {
  148. this.createFirstHalfCrescendoLines(startX, endX, y);
  149. } else {
  150. this.createCrescendoLines(startX, endX, y);
  151. }
  152. }
  153. /**
  154. * Calculate diminuendo lines for system break (full).
  155. * @param startX left most starting point
  156. * @param endX right mist ending point
  157. * @param y y placement
  158. * @param wedgeOpeningLength length of the opening
  159. * @param wedgeLineWidth line width of the wedge
  160. */
  161. public createDiminuendoLines(startX: number, endX: number, y: number,
  162. wedgeOpeningLength: number = this.rules.WedgeOpeningLength, wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  163. const upperWedgeStart: PointF2D = new PointF2D(startX, y - wedgeOpeningLength / 2);
  164. const lowerWedgeStart: PointF2D = new PointF2D(startX, y + wedgeOpeningLength / 2);
  165. const wedgeEnd: PointF2D = new PointF2D(endX, y);
  166. this.addWedgeLines(wedgeEnd, upperWedgeStart, lowerWedgeStart, wedgeLineWidth);
  167. }
  168. /**
  169. * Calculate diminuendo lines for system break (first part).
  170. * @param startX left most starting point
  171. * @param endX right mist ending point
  172. * @param y y placement
  173. * @param wedgeOpeningLength length of the opening
  174. * @param wedgeMeasureEndOpeningLength length of opening at measure end
  175. * @param wedgeLineWidth line width of the wedge
  176. */
  177. public createFirstHalfDiminuendoLines(startX: number, endX: number, y: number,
  178. wedgeOpeningLength: number = this.rules.WedgeOpeningLength,
  179. wedgeMeasureEndOpeningLength: number = this.rules.WedgeMeasureEndOpeningLength,
  180. wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  181. const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeOpeningLength / 2);
  182. const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeOpeningLength / 2);
  183. const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeMeasureEndOpeningLength / 2);
  184. const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeMeasureEndOpeningLength / 2);
  185. this.addDoubleLines(upperLineStart, upperLineEnd, lowerLineStart, lowerLineEnd, wedgeLineWidth);
  186. }
  187. /**
  188. * Calculate diminuendo lines for system break (second part).
  189. * @param startX left most starting point
  190. * @param endX right mist ending point
  191. * @param y y placement
  192. * @param wedgeMeasureBeginOpeningLength length of opening at measure start
  193. * @param wedgeLineWidth line width of the wedge
  194. */
  195. public createSecondHalfDiminuendoLines(startX: number, endX: number, y: number,
  196. wedgeMeasureBeginOpeningLength: number = this.rules.WedgeMeasureBeginOpeningLength,
  197. wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  198. const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeMeasureBeginOpeningLength / 2);
  199. const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeMeasureBeginOpeningLength / 2);
  200. const lineEnd: PointF2D = new PointF2D(endX, y);
  201. this.addWedgeLines(lineEnd, upperLineStart, lowerLineStart, wedgeLineWidth);
  202. }
  203. /**
  204. * This method recalculates the diminuendo lines (for all cases).
  205. * @param startX left most starting point
  206. * @param endX right most ending point
  207. * @param y y placement
  208. */
  209. public recalculateDiminuendoLines(startX: number, endX: number, yPosition: number): void {
  210. const isFirstHalfSplit: boolean = Math.abs(this.lines[0].End.y - this.lines[1].End.y) > 0.0001;
  211. this.lines.clear();
  212. if (isFirstHalfSplit) {
  213. this.createFirstHalfDiminuendoLines(startX, endX, yPosition);
  214. } else if (this.isSplittedPart) {
  215. this.createSecondHalfDiminuendoLines(startX, endX, yPosition);
  216. } else {
  217. this.createDiminuendoLines(startX, endX, yPosition);
  218. }
  219. }
  220. /**
  221. * Calculate the BoundingBox (as a box around the Wedge).
  222. */
  223. public calcPsi(): void {
  224. if (this.IsVerbal) {
  225. this.PositionAndShape.calculateBoundingBox();
  226. return;
  227. }
  228. this.PositionAndShape.RelativePosition = this.lines[0].Start;
  229. this.PositionAndShape.BorderMarginTop = this.lines[0].End.y - this.lines[0].Start.y;
  230. this.PositionAndShape.BorderMarginBottom = this.lines[1].End.y - this.lines[1].Start.y;
  231. if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
  232. this.PositionAndShape.BorderMarginLeft = 0;
  233. this.PositionAndShape.BorderMarginRight = this.lines[0].End.x - this.lines[0].Start.x;
  234. } else {
  235. this.PositionAndShape.BorderMarginLeft = this.lines[0].End.x - this.lines[0].Start.x;
  236. this.PositionAndShape.BorderMarginRight = 0;
  237. }
  238. }
  239. /**
  240. * Clear Lines
  241. */
  242. public cleanUp(): void {
  243. this.lines.clear();
  244. }
  245. /**
  246. * Shift wedge in y position
  247. * @param shift Number to shift
  248. */
  249. public shiftYPosition(shift: number): void {
  250. if (this.IsVerbal) {
  251. this.PositionAndShape.RelativePosition.y += shift;
  252. this.PositionAndShape.calculateBoundingBox();
  253. } else {
  254. this.lines[0].Start.y += shift;
  255. this.lines[0].End.y += shift;
  256. this.lines[1].End.y += shift;
  257. }
  258. }
  259. public squeeze(value: number): void {
  260. // Verbal expressions are not squeezable and squeezing below the width is also not possible
  261. if (this.IsVerbal) {
  262. return;
  263. }
  264. const width: number = Math.abs(this.lines[0].End.x - this.lines[0].Start.x);
  265. if (width < Math.abs(value)) {
  266. return;
  267. }
  268. if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
  269. if (value > 0) {
  270. this.lines[0].Start.x += value;
  271. } else {
  272. this.lines[0].End.x += value;
  273. this.lines[1].End.x += value;
  274. }
  275. } else {
  276. if (value < 0) {
  277. this.lines[0].Start.x += value;
  278. } else {
  279. this.lines[0].End.x += value;
  280. this.lines[1].End.x += value;
  281. }
  282. }
  283. this.calcPsi();
  284. }
  285. //#endregion
  286. //#region Private methods
  287. /**
  288. * Create lines from points and add them to the memory
  289. * @param wedgePoint start of the expression
  290. * @param upperWedgeEnd end of the upper line
  291. * @param lowerWedgeEnd end of lower line
  292. * @param wedgeLineWidth line width
  293. */
  294. private addWedgeLines(wedgePoint: PointF2D, upperWedgeEnd: PointF2D, lowerWedgeEnd: PointF2D, wedgeLineWidth: number): void {
  295. const upperLine: GraphicalLine = new GraphicalLine(wedgePoint, upperWedgeEnd, wedgeLineWidth);
  296. const lowerLine: GraphicalLine = new GraphicalLine(wedgePoint, lowerWedgeEnd, wedgeLineWidth);
  297. this.lines.push(upperLine);
  298. this.lines.push(lowerLine);
  299. }
  300. /**
  301. * Create top and bottom lines for continuing wedges
  302. * @param upperLineStart start of the upper line
  303. * @param upperLineEnd end of the upper line
  304. * @param lowerLineStart start of the lower line
  305. * @param lowerLineEnd end of lower line
  306. * @param wedgeLineWidth line width
  307. */
  308. private addDoubleLines(upperLineStart: PointF2D, upperLineEnd: PointF2D, lowerLineStart: PointF2D, lowerLineEnd: PointF2D, wedgeLineWidth: number): void {
  309. const upperLine: GraphicalLine = new GraphicalLine(upperLineStart, upperLineEnd, wedgeLineWidth);
  310. const lowerLine: GraphicalLine = new GraphicalLine(lowerLineStart, lowerLineEnd, wedgeLineWidth);
  311. this.lines.push(upperLine);
  312. this.lines.push(lowerLine);
  313. }
  314. //#endregion
  315. }