GraphicalContinuousDynamicExpression.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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 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. if (!this.IsVerbal) {
  65. if (this.ContinuousDynamic.DynamicType !== ContDynamicEnum.crescendo &&
  66. this.ContinuousDynamic.DynamicType !== ContDynamicEnum.diminuendo) {
  67. // for now there is only crescendo or decrescendo anyways, but this will catch errors when we add new types in the future
  68. log.warn("GraphicalContinuousDynamicExpression.updateSkyBottomLine(): " +
  69. "unhandled continuous dynamic type. start measure: " + this.startMeasure?.MeasureNumber);
  70. }
  71. }
  72. switch (this.Placement) {
  73. case PlacementEnum.Above:
  74. if (!this.IsVerbal) {
  75. if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
  76. skyBottomLineCalculator.updateSkyLineWithWedge(this.lines[0].Start, this.lines[0].End);
  77. } else if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.diminuendo) {
  78. skyBottomLineCalculator.updateSkyLineWithWedge(this.lines[0].End, this.lines[0].Start);
  79. } // else covered with the log.warn above
  80. } else {
  81. const yValue: number = this.label.PositionAndShape.BorderMarginTop + this.label.PositionAndShape.RelativePosition.y;
  82. skyBottomLineCalculator.updateSkyLineInRange(left, right, yValue);
  83. }
  84. break;
  85. case PlacementEnum.Below:
  86. if (!this.IsVerbal) {
  87. if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
  88. skyBottomLineCalculator.updateBottomLineWithWedge(this.lines[1].Start, this.lines[1].End);
  89. } else if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.diminuendo) {
  90. skyBottomLineCalculator.updateBottomLineWithWedge(this.lines[1].End, this.lines[1].Start);
  91. } // else covered with the log.warn above
  92. } else {
  93. const yValue: number = this.label.PositionAndShape.BorderMarginBottom + this.label.PositionAndShape.RelativePosition.y;
  94. skyBottomLineCalculator.updateBottomLineInRange(left, right, yValue);
  95. }
  96. break;
  97. default:
  98. log.error("Placement for GraphicalContinuousDynamicExpression is unknown");
  99. }
  100. }
  101. /**
  102. * Calculate crescendo lines for (full).
  103. * @param startX left most starting point
  104. * @param endX right mist ending point
  105. * @param y y placement
  106. * @param wedgeOpeningLength length of the opening
  107. * @param wedgeLineWidth line width of the wedge
  108. */
  109. public createCrescendoLines(startX: number, endX: number, y: number,
  110. wedgeOpeningLength: number = this.rules.WedgeOpeningLength, wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  111. const lineStart: PointF2D = new PointF2D(startX, y);
  112. const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeOpeningLength / 2);
  113. const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeOpeningLength / 2);
  114. this.addWedgeLines(lineStart, upperLineEnd, lowerLineEnd, wedgeLineWidth);
  115. }
  116. /**
  117. * Calculate crescendo lines for system break (first part).
  118. * @param startX left most starting point
  119. * @param endX right mist ending point
  120. * @param y y placement
  121. * @param wedgeMeasureEndOpeningLength length of opening at measure end
  122. * @param wedgeOpeningLength length of the opening
  123. * @param wedgeLineWidth line width of the wedge
  124. */
  125. public createFirstHalfCrescendoLines(startX: number, endX: number, y: number,
  126. wedgeMeasureEndOpeningLength: number = this.rules.WedgeMeasureEndOpeningLength,
  127. wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  128. const lineStart: PointF2D = new PointF2D(startX, y);
  129. const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeMeasureEndOpeningLength / 2);
  130. const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeMeasureEndOpeningLength / 2);
  131. this.addWedgeLines(lineStart, upperLineEnd, lowerLineEnd, wedgeLineWidth);
  132. }
  133. /**
  134. * Calculate crescendo lines for system break (second part).
  135. * @param startX left most starting point
  136. * @param endX right mist ending point
  137. * @param y y placement
  138. * @param wedgeMeasureBeginOpeningLength length of opening at measure start
  139. * @param wedgeOpeningLength length of the opening
  140. * @param wedgeLineWidth line width of the wedge
  141. */
  142. public createSecondHalfCrescendoLines(startX: number, endX: number, y: number,
  143. wedgeMeasureBeginOpeningLength: number = this.rules.WedgeMeasureBeginOpeningLength,
  144. wedgeOpeningLength: number = this.rules.WedgeOpeningLength,
  145. wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  146. const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeMeasureBeginOpeningLength / 2);
  147. const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeMeasureBeginOpeningLength / 2);
  148. const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeOpeningLength / 2);
  149. const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeOpeningLength / 2);
  150. this.addDoubleLines(upperLineStart, upperLineEnd, lowerLineStart, lowerLineEnd, wedgeLineWidth);
  151. }
  152. /**
  153. * This method recalculates the Crescendo Lines (for all cases).
  154. * @param startX left most starting point
  155. * @param endX right most ending point
  156. * @param y y placement
  157. */
  158. public recalculateCrescendoLines(startX: number, endX: number, y: number): void {
  159. const isSecondHalfSplit: boolean = Math.abs(this.lines[0].Start.y - this.lines[1].Start.y) > 0.0001;
  160. this.lines.clear();
  161. if (isSecondHalfSplit) {
  162. this.createSecondHalfCrescendoLines(startX, endX, y);
  163. } else if (this.isSplittedPart) {
  164. this.createFirstHalfCrescendoLines(startX, endX, y);
  165. } else {
  166. this.createCrescendoLines(startX, endX, y);
  167. }
  168. }
  169. /**
  170. * Calculate diminuendo lines for system break (full).
  171. * @param startX left most starting point
  172. * @param endX right mist ending point
  173. * @param y y placement
  174. * @param wedgeOpeningLength length of the opening
  175. * @param wedgeLineWidth line width of the wedge
  176. */
  177. public createDiminuendoLines(startX: number, endX: number, y: number,
  178. wedgeOpeningLength: number = this.rules.WedgeOpeningLength, wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  179. const upperWedgeStart: PointF2D = new PointF2D(startX, y - wedgeOpeningLength / 2);
  180. const lowerWedgeStart: PointF2D = new PointF2D(startX, y + wedgeOpeningLength / 2);
  181. const wedgeEnd: PointF2D = new PointF2D(endX, y);
  182. this.addWedgeLines(wedgeEnd, upperWedgeStart, lowerWedgeStart, wedgeLineWidth);
  183. }
  184. /**
  185. * Calculate diminuendo lines for system break (first part).
  186. * @param startX left most starting point
  187. * @param endX right mist ending point
  188. * @param y y placement
  189. * @param wedgeOpeningLength length of the opening
  190. * @param wedgeMeasureEndOpeningLength length of opening at measure end
  191. * @param wedgeLineWidth line width of the wedge
  192. */
  193. public createFirstHalfDiminuendoLines(startX: number, endX: number, y: number,
  194. wedgeOpeningLength: number = this.rules.WedgeOpeningLength,
  195. wedgeMeasureEndOpeningLength: number = this.rules.WedgeMeasureEndOpeningLength,
  196. wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  197. const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeOpeningLength / 2);
  198. const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeOpeningLength / 2);
  199. const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeMeasureEndOpeningLength / 2);
  200. const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeMeasureEndOpeningLength / 2);
  201. this.addDoubleLines(upperLineStart, upperLineEnd, lowerLineStart, lowerLineEnd, wedgeLineWidth);
  202. }
  203. /**
  204. * Calculate diminuendo lines for system break (second part).
  205. * @param startX left most starting point
  206. * @param endX right mist ending point
  207. * @param y y placement
  208. * @param wedgeMeasureBeginOpeningLength length of opening at measure start
  209. * @param wedgeLineWidth line width of the wedge
  210. */
  211. public createSecondHalfDiminuendoLines(startX: number, endX: number, y: number,
  212. wedgeMeasureBeginOpeningLength: number = this.rules.WedgeMeasureBeginOpeningLength,
  213. wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
  214. const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeMeasureBeginOpeningLength / 2);
  215. const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeMeasureBeginOpeningLength / 2);
  216. const lineEnd: PointF2D = new PointF2D(endX, y);
  217. this.addWedgeLines(lineEnd, upperLineStart, lowerLineStart, wedgeLineWidth);
  218. }
  219. /**
  220. * This method recalculates the diminuendo lines (for all cases).
  221. * @param startX left most starting point
  222. * @param endX right most ending point
  223. * @param y y placement
  224. */
  225. public recalculateDiminuendoLines(startX: number, endX: number, yPosition: number): void {
  226. const isFirstHalfSplit: boolean = Math.abs(this.lines[0].End.y - this.lines[1].End.y) > 0.0001;
  227. this.lines.clear();
  228. if (isFirstHalfSplit) {
  229. this.createFirstHalfDiminuendoLines(startX, endX, yPosition);
  230. } else if (this.isSplittedPart) {
  231. this.createSecondHalfDiminuendoLines(startX, endX, yPosition);
  232. } else {
  233. this.createDiminuendoLines(startX, endX, yPosition);
  234. }
  235. }
  236. /**
  237. * Calculate the BoundingBox (as a box around the Wedge).
  238. */
  239. public calcPsi(): void {
  240. if (this.IsVerbal) {
  241. this.PositionAndShape.calculateBoundingBox();
  242. return;
  243. }
  244. this.PositionAndShape.RelativePosition = this.lines[0].Start;
  245. this.PositionAndShape.BorderMarginTop = this.lines[0].End.y - this.lines[0].Start.y;
  246. this.PositionAndShape.BorderMarginBottom = this.lines[1].End.y - this.lines[1].Start.y;
  247. if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
  248. this.PositionAndShape.BorderMarginLeft = 0;
  249. this.PositionAndShape.BorderMarginRight = this.lines[0].End.x - this.lines[0].Start.x;
  250. } else {
  251. this.PositionAndShape.BorderMarginLeft = this.lines[0].End.x - this.lines[0].Start.x;
  252. this.PositionAndShape.BorderMarginRight = 0;
  253. }
  254. }
  255. /**
  256. * Clear Lines
  257. */
  258. public cleanUp(): void {
  259. this.lines.clear();
  260. }
  261. /**
  262. * Shift wedge in y position
  263. * @param shift Number to shift
  264. */
  265. public shiftYPosition(shift: number): void {
  266. if (this.IsVerbal) {
  267. this.PositionAndShape.RelativePosition.y += shift;
  268. this.PositionAndShape.calculateBoundingBox();
  269. } else {
  270. this.lines[0].Start.y += shift;
  271. this.lines[0].End.y += shift;
  272. this.lines[1].End.y += shift;
  273. }
  274. }
  275. public squeeze(value: number): void {
  276. // Verbal expressions are not squeezable and squeezing below the width is also not possible
  277. if (this.IsVerbal) {
  278. return;
  279. }
  280. const width: number = Math.abs(this.lines[0].End.x - this.lines[0].Start.x);
  281. if (width < Math.abs(value)) {
  282. return;
  283. }
  284. if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
  285. if (value > 0) {
  286. this.lines[0].Start.x += value;
  287. } else {
  288. this.lines[0].End.x += value;
  289. this.lines[1].End.x += value;
  290. }
  291. } else {
  292. if (value < 0) {
  293. this.lines[0].Start.x += value;
  294. } else {
  295. this.lines[0].End.x += value;
  296. this.lines[1].End.x += value;
  297. }
  298. }
  299. this.calcPsi();
  300. }
  301. //#endregion
  302. //#region Private methods
  303. /**
  304. * Create lines from points and add them to the memory
  305. * @param wedgePoint start of the expression
  306. * @param upperWedgeEnd end of the upper line
  307. * @param lowerWedgeEnd end of lower line
  308. * @param wedgeLineWidth line width
  309. */
  310. private addWedgeLines(wedgePoint: PointF2D, upperWedgeEnd: PointF2D, lowerWedgeEnd: PointF2D, wedgeLineWidth: number): void {
  311. const upperLine: GraphicalLine = new GraphicalLine(wedgePoint, upperWedgeEnd, wedgeLineWidth);
  312. const lowerLine: GraphicalLine = new GraphicalLine(wedgePoint, lowerWedgeEnd, wedgeLineWidth);
  313. this.lines.push(upperLine);
  314. this.lines.push(lowerLine);
  315. }
  316. /**
  317. * Create top and bottom lines for continuing wedges
  318. * @param upperLineStart start of the upper line
  319. * @param upperLineEnd end of the upper line
  320. * @param lowerLineStart start of the lower line
  321. * @param lowerLineEnd end of lower line
  322. * @param wedgeLineWidth line width
  323. */
  324. private addDoubleLines(upperLineStart: PointF2D, upperLineEnd: PointF2D, lowerLineStart: PointF2D, lowerLineEnd: PointF2D, wedgeLineWidth: number): void {
  325. const upperLine: GraphicalLine = new GraphicalLine(upperLineStart, upperLineEnd, wedgeLineWidth);
  326. const lowerLine: GraphicalLine = new GraphicalLine(lowerLineStart, lowerLineEnd, wedgeLineWidth);
  327. this.lines.push(upperLine);
  328. this.lines.push(lowerLine);
  329. }
  330. //#endregion
  331. }