GraphicalContinuousDynamicExpression.ts 18 KB

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