GraphicalMusicSheet.ts 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035
  1. import {MusicSheet} from "../MusicSheet";
  2. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  3. import {GraphicalMeasure} from "./GraphicalMeasure";
  4. import {GraphicalMusicPage} from "./GraphicalMusicPage";
  5. import {VerticalGraphicalStaffEntryContainer} from "./VerticalGraphicalStaffEntryContainer";
  6. import {GraphicalLabel} from "./GraphicalLabel";
  7. import {GraphicalLine} from "./GraphicalLine";
  8. import {MusicSystem} from "./MusicSystem";
  9. import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
  10. import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
  11. import {PointF2D} from "../../Common/DataObjects/PointF2D";
  12. import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
  13. import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
  14. import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
  15. import {Fraction} from "../../Common/DataObjects/Fraction";
  16. import {GraphicalNote} from "./GraphicalNote";
  17. import {Instrument} from "../Instrument";
  18. import {BoundingBox} from "./BoundingBox";
  19. import {MusicSheetCalculator} from "./MusicSheetCalculator";
  20. import log from "loglevel";
  21. import {CollectionUtil} from "../../Util/CollectionUtil";
  22. import {SelectionStartSymbol} from "./SelectionStartSymbol";
  23. import {SelectionEndSymbol} from "./SelectionEndSymbol";
  24. import {OutlineAndFillStyleEnum} from "./DrawingEnums";
  25. import { Clickable } from "./Clickable";
  26. import { StaffLine } from "./StaffLine";
  27. import { MusicSheetDrawer } from "./MusicSheetDrawer";
  28. import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
  29. import { GraphicalObject } from "./GraphicalObject";
  30. // import { VexFlowMusicSheetDrawer } from "./VexFlow/VexFlowMusicSheetDrawer";
  31. // import { SvgVexFlowBackend } from "./VexFlow/SvgVexFlowBackend"; // causes build problem with npm start
  32. /**
  33. * The graphical counterpart of a [[MusicSheet]]
  34. */
  35. export class GraphicalMusicSheet {
  36. constructor(musicSheet: MusicSheet, calculator: MusicSheetCalculator) {
  37. this.musicSheet = musicSheet;
  38. this.numberOfStaves = this.musicSheet.Staves.length;
  39. this.calculator = calculator;
  40. this.calculator.initialize(this);
  41. }
  42. private musicSheet: MusicSheet;
  43. //private fontInfo: FontInfo = FontInfo.Info;
  44. private calculator: MusicSheetCalculator;
  45. public drawer: MusicSheetDrawer;
  46. private musicPages: GraphicalMusicPage[] = [];
  47. /** measures (i,j) where i is the measure number and j the staff index (e.g. staff indices 0, 1 for two piano parts) */
  48. private measureList: GraphicalMeasure[][] = [];
  49. private verticalGraphicalStaffEntryContainers: VerticalGraphicalStaffEntryContainer[] = [];
  50. private title: GraphicalLabel;
  51. private subtitle: GraphicalLabel;
  52. private composer: GraphicalLabel;
  53. private lyricist: GraphicalLabel;
  54. private copyright: GraphicalLabel;
  55. private cursors: GraphicalLine[] = [];
  56. private selectionStartSymbol: SelectionStartSymbol;
  57. private selectionEndSymbol: SelectionEndSymbol;
  58. private minAllowedSystemWidth: number;
  59. //private systemImages: Dictionary<MusicSystem, SystemImageProperties> = new Dictionary<MusicSystem, SystemImageProperties>();
  60. private numberOfStaves: number;
  61. private leadSheet: boolean = false;
  62. public get ParentMusicSheet(): MusicSheet {
  63. return this.musicSheet;
  64. }
  65. public get GetCalculator(): MusicSheetCalculator {
  66. return this.calculator;
  67. }
  68. public get MusicPages(): GraphicalMusicPage[] {
  69. return this.musicPages;
  70. }
  71. public set MusicPages(value: GraphicalMusicPage[]) {
  72. this.musicPages = value;
  73. }
  74. //public get FontInfo(): FontInfo {
  75. // return this.fontInfo;
  76. //}
  77. public get MeasureList(): GraphicalMeasure[][] {
  78. return this.measureList;
  79. }
  80. public set MeasureList(value: GraphicalMeasure[][]) {
  81. this.measureList = value;
  82. }
  83. public get VerticalGraphicalStaffEntryContainers(): VerticalGraphicalStaffEntryContainer[] {
  84. return this.verticalGraphicalStaffEntryContainers;
  85. }
  86. public set VerticalGraphicalStaffEntryContainers(value: VerticalGraphicalStaffEntryContainer[]) {
  87. this.verticalGraphicalStaffEntryContainers = value;
  88. }
  89. public get Title(): GraphicalLabel {
  90. return this.title;
  91. }
  92. public set Title(value: GraphicalLabel) {
  93. this.title = value;
  94. }
  95. public get Subtitle(): GraphicalLabel {
  96. return this.subtitle;
  97. }
  98. public set Subtitle(value: GraphicalLabel) {
  99. this.subtitle = value;
  100. }
  101. public get Composer(): GraphicalLabel {
  102. return this.composer;
  103. }
  104. public set Composer(value: GraphicalLabel) {
  105. this.composer = value;
  106. }
  107. public get Lyricist(): GraphicalLabel {
  108. return this.lyricist;
  109. }
  110. public set Lyricist(value: GraphicalLabel) {
  111. this.lyricist = value;
  112. }
  113. public get Copyright(): GraphicalLabel {
  114. return this.copyright;
  115. }
  116. public set Copyright(value: GraphicalLabel) {
  117. this.copyright = value;
  118. }
  119. public get Cursors(): GraphicalLine[] {
  120. return this.cursors;
  121. }
  122. public get SelectionStartSymbol(): SelectionStartSymbol {
  123. return this.selectionStartSymbol;
  124. }
  125. public get SelectionEndSymbol(): SelectionEndSymbol {
  126. return this.selectionEndSymbol;
  127. }
  128. public get MinAllowedSystemWidth(): number {
  129. return this.minAllowedSystemWidth;
  130. }
  131. public set MinAllowedSystemWidth(value: number) {
  132. this.minAllowedSystemWidth = value;
  133. }
  134. // public get SystemImages(): Dictionary<MusicSystem, SystemImageProperties> {
  135. // return this.systemImages;
  136. // }
  137. public get NumberOfStaves(): number {
  138. return this.numberOfStaves;
  139. }
  140. public get LeadSheet(): boolean {
  141. return this.leadSheet;
  142. }
  143. public set LeadSheet(value: boolean) {
  144. this.leadSheet = value;
  145. }
  146. /**
  147. * Calculate the Absolute Positions from the Relative Positions.
  148. * @param graphicalMusicSheet
  149. */
  150. public static transformRelativeToAbsolutePosition(graphicalMusicSheet: GraphicalMusicSheet): void {
  151. for (let i: number = 0; i < graphicalMusicSheet.MusicPages.length; i++) {
  152. const pageAbsolute: PointF2D = graphicalMusicSheet.MusicPages[i].setMusicPageAbsolutePosition(i, graphicalMusicSheet.ParentMusicSheet.Rules);
  153. const page: GraphicalMusicPage = graphicalMusicSheet.MusicPages[i];
  154. page.PositionAndShape.calculateAbsolutePositionsRecursive(pageAbsolute.x, pageAbsolute.y);
  155. }
  156. }
  157. public Initialize(): void {
  158. this.verticalGraphicalStaffEntryContainers = [];
  159. this.musicPages = [];
  160. this.measureList = [];
  161. }
  162. public reCalculate(): void {
  163. this.calculator.calculate();
  164. }
  165. // unused method
  166. // public prepare(): void {
  167. // this.calculator.prepareGraphicalMusicSheet();
  168. // }
  169. public EnforceRedrawOfMusicSystems(): void {
  170. for (let idx: number = 0, len: number = this.musicPages.length; idx < len; ++idx) {
  171. const graphicalMusicPage: GraphicalMusicPage = this.musicPages[idx];
  172. for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
  173. const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
  174. musicSystem.needsToBeRedrawn = true;
  175. }
  176. }
  177. }
  178. public getClickedObject<T>(positionOnMusicSheet: PointF2D): T {
  179. for (let idx: number = 0, len: number = this.MusicPages.length; idx < len; ++idx) {
  180. const graphicalMusicPage: GraphicalMusicPage = this.MusicPages[idx];
  181. return graphicalMusicPage.PositionAndShape.getClickedObjectOfType<T>(positionOnMusicSheet);
  182. }
  183. return undefined;
  184. }
  185. public findGraphicalMeasure(measureIndex: number, staffIndex: number): GraphicalMeasure {
  186. // note the cursor calls this with measureIndex 1 (measure 2) when advancing beyond the end of a 1-measure piece
  187. for (let i: number = measureIndex; i >= 0; i--) {
  188. const gMeasure: GraphicalMeasure = this.measureList[i]?.[staffIndex];
  189. if (gMeasure) {
  190. return gMeasure;
  191. }
  192. // else look backwards (previous measures). this is only really valid for MultipleRestMeasures of course.
  193. }
  194. return undefined; // shouldn't happen
  195. }
  196. /**
  197. * Search the MeasureList for a certain GraphicalStaffEntry with the given SourceStaffEntry,
  198. * at a certain verticalIndex (eg a corresponding Staff), starting at a specific horizontalIndex (eg specific GraphicalMeasure).
  199. * @param staffIndex
  200. * @param measureIndex
  201. * @param sourceStaffEntry
  202. * @returns {any}
  203. */
  204. public findGraphicalStaffEntryFromMeasureList(staffIndex: number, measureIndex: number, sourceStaffEntry: SourceStaffEntry): GraphicalStaffEntry {
  205. for (let i: number = measureIndex; i < this.measureList.length; i++) {
  206. const graphicalMeasure: GraphicalMeasure = this.measureList[i][staffIndex];
  207. if (!graphicalMeasure) {
  208. continue;
  209. }
  210. for (let idx: number = 0, len: number = graphicalMeasure.staffEntries.length; idx < len; ++idx) {
  211. const graphicalStaffEntry: GraphicalStaffEntry = graphicalMeasure.staffEntries[idx];
  212. if (graphicalStaffEntry.sourceStaffEntry === sourceStaffEntry) {
  213. return graphicalStaffEntry;
  214. }
  215. }
  216. }
  217. return undefined;
  218. }
  219. /**
  220. * Return the next (to the right) not null GraphicalStaffEntry from a given Index.
  221. * @param staffIndex
  222. * @param measureIndex
  223. * @param graphicalStaffEntry
  224. * @returns {any}
  225. */
  226. public findNextGraphicalStaffEntry(staffIndex: number, measureIndex: number, graphicalStaffEntry: GraphicalStaffEntry): GraphicalStaffEntry {
  227. const graphicalMeasure: GraphicalMeasure = graphicalStaffEntry.parentMeasure;
  228. const graphicalStaffEntryIndex: number = graphicalMeasure.staffEntries.indexOf(graphicalStaffEntry);
  229. if (graphicalStaffEntryIndex < graphicalMeasure.staffEntries.length - 1) {
  230. return graphicalMeasure.staffEntries[graphicalStaffEntryIndex + 1];
  231. } else if (measureIndex < this.measureList.length - 1) {
  232. const nextMeasure: GraphicalMeasure = this.measureList[measureIndex + 1][staffIndex];
  233. if (nextMeasure.staffEntries.length > 0) {
  234. return nextMeasure.staffEntries[0];
  235. }
  236. }
  237. return undefined;
  238. }
  239. public getFirstVisibleMeasuresListFromIndices(start: number, end: number): GraphicalMeasure[] {
  240. const graphicalMeasures: GraphicalMeasure[] = [];
  241. const numberOfStaves: number = this.measureList[0].length;
  242. for (let i: number = start; i <= end; i++) {
  243. for (let j: number = 0; j < numberOfStaves; j++) {
  244. if (this.measureList[i][j].isVisible()) {
  245. graphicalMeasures.push(this.measureList[i][j]);
  246. break;
  247. }
  248. }
  249. }
  250. return graphicalMeasures;
  251. }
  252. public orderMeasuresByStaffLine(measures: GraphicalMeasure[]): GraphicalMeasure[][] {
  253. const orderedMeasures: GraphicalMeasure[][] = [];
  254. let mList: GraphicalMeasure[] = [];
  255. orderedMeasures.push(mList);
  256. for (let i: number = 0; i < measures.length; i++) {
  257. if (i === 0) {
  258. mList.push(measures[0]);
  259. } else {
  260. if (measures[i].ParentStaffLine === measures[i - 1].ParentStaffLine) {
  261. mList.push(measures[i]);
  262. } else {
  263. if (orderedMeasures.indexOf(mList) === -1) {
  264. orderedMeasures.push(mList);
  265. }
  266. mList = [];
  267. orderedMeasures.push(mList);
  268. mList.push(measures[i]);
  269. }
  270. }
  271. }
  272. return orderedMeasures;
  273. }
  274. /**
  275. * Return the active Clefs at the start of the first SourceMeasure.
  276. * @returns {ClefInstruction[]}
  277. */
  278. public initializeActiveClefs(): ClefInstruction[] {
  279. const activeClefs: ClefInstruction[] = [];
  280. const firstSourceMeasure: SourceMeasure = this.musicSheet.getFirstSourceMeasure();
  281. if (firstSourceMeasure) {
  282. for (let i: number = 0; i < firstSourceMeasure.CompleteNumberOfStaves; i++) {
  283. let clef: ClefInstruction = new ClefInstruction();
  284. if (firstSourceMeasure.FirstInstructionsStaffEntries[i]) {
  285. for (let idx: number = 0, len: number = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions.length; idx < len; ++idx) {
  286. const abstractNotationInstruction: AbstractNotationInstruction = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions[idx];
  287. if (abstractNotationInstruction instanceof ClefInstruction) {
  288. clef = <ClefInstruction>abstractNotationInstruction;
  289. }
  290. }
  291. }
  292. activeClefs.push(clef);
  293. }
  294. }
  295. return activeClefs;
  296. }
  297. public GetMainKey(): KeyInstruction {
  298. const firstSourceMeasure: SourceMeasure = this.musicSheet.getFirstSourceMeasure();
  299. if (firstSourceMeasure) {
  300. for (let i: number = 0; i < firstSourceMeasure.CompleteNumberOfStaves; i++) {
  301. for (let idx: number = 0, len: number = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions.length; idx < len; ++idx) {
  302. const abstractNotationInstruction: AbstractNotationInstruction = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions[idx];
  303. if (abstractNotationInstruction instanceof KeyInstruction) {
  304. return <KeyInstruction>abstractNotationInstruction;
  305. }
  306. }
  307. }
  308. }
  309. return undefined;
  310. }
  311. /**
  312. * Create the VerticalContainer and adds it to the List at the correct Timestamp position.
  313. * @param timestamp
  314. * @returns {any}
  315. */
  316. public getOrCreateVerticalContainer(timestamp: Fraction): VerticalGraphicalStaffEntryContainer {
  317. if (this.verticalGraphicalStaffEntryContainers.length === 0 ||
  318. (CollectionUtil.getLastElement(this.verticalGraphicalStaffEntryContainers).AbsoluteTimestamp).lt(timestamp)) {
  319. const verticalGraphicalStaffEntryContainer: VerticalGraphicalStaffEntryContainer =
  320. new VerticalGraphicalStaffEntryContainer(this.numberOfStaves, timestamp);
  321. this.verticalGraphicalStaffEntryContainers.push(verticalGraphicalStaffEntryContainer);
  322. return verticalGraphicalStaffEntryContainer;
  323. }
  324. for (let i: number = this.verticalGraphicalStaffEntryContainers.length - 1; i >= 0; i--) {
  325. if (this.verticalGraphicalStaffEntryContainers[i].AbsoluteTimestamp.lt(timestamp)) {
  326. const verticalGraphicalStaffEntryContainer: VerticalGraphicalStaffEntryContainer =
  327. new VerticalGraphicalStaffEntryContainer(this.numberOfStaves, timestamp);
  328. this.verticalGraphicalStaffEntryContainers.splice(i + 1, 0, verticalGraphicalStaffEntryContainer);
  329. return verticalGraphicalStaffEntryContainer;
  330. }
  331. if (this.verticalGraphicalStaffEntryContainers[i].AbsoluteTimestamp.Equals(timestamp)) {
  332. return this.verticalGraphicalStaffEntryContainers[i];
  333. }
  334. }
  335. return undefined;
  336. }
  337. /**
  338. * Does a binary search on the container list and returns the VerticalContainer with the given Timestamp.
  339. * The search begins at startIndex, if given.
  340. * If the timestamp cannot be found, null is returned.
  341. * @param timestamp - The timestamp for which the container shall be found.
  342. * @param startIndex - The index from which the search starts in the container list.
  343. * @returns {any}
  344. * @constructor
  345. */
  346. public GetVerticalContainerFromTimestamp(timestamp: Fraction, startIndex: number = 0): VerticalGraphicalStaffEntryContainer {
  347. const index: number = CollectionUtil.binarySearch(this.verticalGraphicalStaffEntryContainers,
  348. new VerticalGraphicalStaffEntryContainer(0, timestamp),
  349. VerticalGraphicalStaffEntryContainer.compareByTimestamp,
  350. startIndex);
  351. if (index >= 0) {
  352. return this.verticalGraphicalStaffEntryContainers[index];
  353. }
  354. return undefined;
  355. }
  356. /**
  357. * Perform a binary search for the absolute given Timestamp in all the GraphicalVerticalContainers.
  358. * @param musicTimestamp
  359. * @returns {number}
  360. * @constructor
  361. */
  362. public GetInterpolatedIndexInVerticalContainers(musicTimestamp: Fraction): number {
  363. const containers: VerticalGraphicalStaffEntryContainer[] = this.verticalGraphicalStaffEntryContainers;
  364. if (containers.length === 1) {
  365. return 0; // this fixes an error with Noteflight samples, see below (#1473). It may also be faster.
  366. }
  367. let leftIndex: number = 0;
  368. let rightIndex: number = containers.length - 1;
  369. let leftTS: Fraction = undefined;
  370. let rightTS: Fraction = undefined;
  371. // TODO AbsoluteTimestamp can be NaN in some erroneous MusicXML files like from Noteflight, see omd issue #1473
  372. // (though in the sample tested, there is only one container, so above containers.length === 1 prevents the error)
  373. if (musicTimestamp.lte(containers[containers.length - 1].AbsoluteTimestamp)) {
  374. while (rightIndex - leftIndex > 1) {
  375. const middleIndex: number = Math.floor((rightIndex + leftIndex) / 2);
  376. if (containers[leftIndex].AbsoluteTimestamp.Equals(musicTimestamp)) {
  377. rightIndex = leftIndex;
  378. break;
  379. } else if (containers[rightIndex].AbsoluteTimestamp.Equals(musicTimestamp)) {
  380. leftIndex = rightIndex;
  381. break;
  382. } else if (containers[middleIndex].AbsoluteTimestamp.Equals(musicTimestamp)) {
  383. return this.verticalGraphicalStaffEntryContainers.indexOf(containers[middleIndex]);
  384. } else if (musicTimestamp.lt(containers[middleIndex].AbsoluteTimestamp)) {
  385. rightIndex = middleIndex;
  386. } else {
  387. leftIndex = middleIndex;
  388. }
  389. }
  390. // no interpolation needed
  391. if (leftIndex === rightIndex) {
  392. return this.verticalGraphicalStaffEntryContainers.indexOf(containers[leftIndex]);
  393. }
  394. leftTS = containers[leftIndex].AbsoluteTimestamp;
  395. rightTS = containers[rightIndex].AbsoluteTimestamp;
  396. } else {
  397. leftTS = containers[containers.length - 1].AbsoluteTimestamp;
  398. rightTS = Fraction.plus(this.getLongestStaffEntryDuration(containers.length - 1), leftTS);
  399. rightIndex = containers.length;
  400. }
  401. const diff: number = rightTS.RealValue - leftTS.RealValue;
  402. const diffTS: number = rightTS.RealValue - musicTimestamp.RealValue;
  403. // estimate the interpolated index
  404. const foundIndex: number = rightIndex - (diffTS / diff);
  405. return Math.min(foundIndex, this.verticalGraphicalStaffEntryContainers.length - 1);
  406. }
  407. /**
  408. * Get a List with the indices of all the visible GraphicalMeasures and calculates their
  409. * corresponding indices in the first SourceMeasure, taking into account Instruments with multiple Staves.
  410. * @param visibleMeasures
  411. * @returns {number[]}
  412. */
  413. public getVisibleStavesIndicesFromSourceMeasure(visibleMeasures: GraphicalMeasure[]): number[] {
  414. const visibleInstruments: Instrument[] = [];
  415. const visibleStavesIndices: number[] = [];
  416. for (let idx: number = 0, len: number = visibleMeasures.length; idx < len; ++idx) {
  417. const graphicalMeasure: GraphicalMeasure = visibleMeasures[idx];
  418. const instrument: Instrument = graphicalMeasure.ParentStaff.ParentInstrument;
  419. if (visibleInstruments.indexOf(instrument) === -1) {
  420. visibleInstruments.push(instrument);
  421. }
  422. }
  423. for (let idx: number = 0, len: number = visibleInstruments.length; idx < len; ++idx) {
  424. const instrument: Instrument = visibleInstruments[idx];
  425. const index: number = this.musicSheet.getGlobalStaffIndexOfFirstStaff(instrument);
  426. for (let j: number = 0; j < instrument.Staves.length; j++) {
  427. visibleStavesIndices.push(index + j);
  428. }
  429. }
  430. return visibleStavesIndices;
  431. }
  432. /**
  433. * Returns the GraphicalMeasure with the given SourceMeasure as Parent at the given staff index.
  434. * @param sourceMeasure
  435. * @param staffIndex
  436. * @returns {any}
  437. */
  438. public getGraphicalMeasureFromSourceMeasureAndIndex(sourceMeasure: SourceMeasure, staffIndex: number): GraphicalMeasure {
  439. for (let i: number = 0; i < this.measureList.length; i++) {
  440. if (this.measureList[i][0]?.parentSourceMeasure === sourceMeasure) {
  441. return this.measureList[i][staffIndex];
  442. }
  443. }
  444. return undefined;
  445. }
  446. public getLastGraphicalMeasureFromIndex(staffIndex: number, lastRendered: boolean = true): GraphicalMeasure {
  447. let measureIndex: number = this.measureList.length - 1;
  448. if (lastRendered) {
  449. measureIndex = Math.min(measureIndex, this.musicSheet.Rules.MaxMeasureToDrawIndex);
  450. }
  451. let measure: GraphicalMeasure = this.measureList[measureIndex][staffIndex];
  452. while (!measure && measureIndex >= 0) { // check for undefined measures, e.g. multi-measure-rest
  453. measure = this.measureList[--measureIndex][staffIndex];
  454. }
  455. return this.measureList[measureIndex][staffIndex];
  456. }
  457. public getMeasureIndex(graphicalMeasure: GraphicalMeasure, measureIndex: number, inListIndex: number): boolean {
  458. measureIndex = 0;
  459. inListIndex = 0;
  460. for (; measureIndex < this.measureList.length; measureIndex++) {
  461. for (let idx: number = 0, len: number = this.measureList[measureIndex].length; idx < len; ++idx) {
  462. const measure: GraphicalMeasure = this.measureList[measureIndex][idx];
  463. if (measure === graphicalMeasure) {
  464. return true;
  465. }
  466. }
  467. }
  468. return false;
  469. }
  470. /**
  471. * Generic method to find graphical objects on the sheet at a given location.
  472. * @param clickPosition Position in units where we are searching on the sheet
  473. * @param className String representation of the class we want to find. Must extend GraphicalObject
  474. * @param startSearchArea The area in units around our point to look for our graphical object, default 5
  475. * @param maxSearchArea The max area we want to search around our point
  476. * @param searchAreaIncrement The amount we expand our search area for each iteration that we don't find an object of the given type
  477. * @param shouldBeIncludedTest A callback that determines if the object should be included in our results- return false for no, true for yes
  478. */
  479. private GetNearestGraphicalObject<T extends GraphicalObject>(
  480. clickPosition: PointF2D, className: string = GraphicalObject.name,
  481. startSearchArea: number = 5, maxSearchArea: number = 20, searchAreaIncrement: number = 5,
  482. shouldBeIncludedTest: (objectToTest: T) => boolean = undefined): T {
  483. const foundEntries: T[] = [];
  484. //Loop until we find some, or our search area is out of bounds
  485. while (foundEntries.length === 0 && startSearchArea <= maxSearchArea) {
  486. //Prepare search area
  487. const region: BoundingBox = new BoundingBox(undefined);
  488. region.BorderLeft = clickPosition.x - startSearchArea;
  489. region.BorderTop = clickPosition.y - startSearchArea;
  490. region.BorderRight = clickPosition.x + startSearchArea;
  491. region.BorderBottom = clickPosition.y + startSearchArea;
  492. region.AbsolutePosition = new PointF2D(clickPosition.x, clickPosition.y);
  493. region.calculateAbsolutePosition();
  494. //Loop through music pages
  495. for (let idx: number = 0, len: number = this.MusicPages.length; idx < len; ++idx) {
  496. const graphicalMusicPage: GraphicalMusicPage = this.MusicPages[idx];
  497. const entries: T[] = graphicalMusicPage.PositionAndShape.getObjectsInRegion<T>(region, false, className);
  498. //If we have no entries on this page, skip to next (if exists)
  499. if (!entries || entries.length === 0) {
  500. continue;
  501. } else {
  502. //Otherwise test all our entries if applicable, store on our found list
  503. for (let idx2: number = 0, len2: number = entries.length; idx2 < len2; ++idx2) {
  504. if (!shouldBeIncludedTest) {
  505. foundEntries.push(entries[idx2]);
  506. } else if (shouldBeIncludedTest(entries[idx2])) {
  507. foundEntries.push(entries[idx2]);
  508. }
  509. }
  510. }
  511. }
  512. //Expand search area, we haven't found anything yet
  513. startSearchArea += searchAreaIncrement;
  514. }
  515. // Get closest entry
  516. let closest: T = undefined;
  517. for (let idx: number = 0, len: number = foundEntries.length; idx < len; ++idx) {
  518. const object: T = foundEntries[idx];
  519. if (closest === undefined) {
  520. closest = object;
  521. } else {
  522. const deltaNew: number = this.CalculateDistance(object.PositionAndShape.AbsolutePosition, clickPosition);
  523. const deltaOld: number = this.CalculateDistance(closest.PositionAndShape.AbsolutePosition, clickPosition);
  524. if (deltaNew < deltaOld) {
  525. closest = object;
  526. }
  527. }
  528. }
  529. if (closest) {
  530. return closest;
  531. }
  532. return undefined;
  533. }
  534. public GetNearestVoiceEntry(clickPosition: PointF2D): GraphicalVoiceEntry {
  535. return this.GetNearestGraphicalObject<GraphicalVoiceEntry>(clickPosition, GraphicalVoiceEntry.name, 5, 20, 5,
  536. (object: GraphicalVoiceEntry) =>
  537. object.parentStaffEntry?.relInMeasureTimestamp !== undefined);
  538. }
  539. public GetNearestNote(clickPosition: PointF2D, maxClickDist: PointF2D): GraphicalNote {
  540. // return this.GetNearestGraphicalObject<GraphicalNote>
  541. // (clickPosition, GraphicalNote.name, 10, 10, 1,
  542. // function(note: GraphicalNote): boolean {
  543. // if (Math.abs(note.PositionAndShape.AbsolutePosition.x - clickPosition.x) < maxClickDist.x
  544. // && Math.abs(note.PositionAndShape.AbsolutePosition.y - clickPosition.y) < maxClickDist.y) {
  545. // return true;
  546. // } else {
  547. // return false;
  548. // }
  549. // });
  550. //TODO: This functions a little bit different than our GetGraphicalObject method... Perhaps close enough to consolidate
  551. //Investigate
  552. const nearestVoiceEntry: GraphicalVoiceEntry = this.GetNearestVoiceEntry(clickPosition);
  553. if (!nearestVoiceEntry) {
  554. return undefined;
  555. }
  556. let closestNote: GraphicalNote;
  557. let closestDist: number = Number.MAX_SAFE_INTEGER;
  558. // debug: show position in sheet. line starts from the click position, until clickposition.x + 2
  559. // (this.drawer as any).DrawOverlayLine( // as VexFlowMusicSheetDrawer
  560. // clickPosition,
  561. // new PointF2D(clickPosition.x + 2, clickPosition.y),
  562. // this.MusicPages[0]);
  563. for (const note of nearestVoiceEntry.notes) {
  564. const posY: number = note.PositionAndShape.AbsolutePosition.y;
  565. const distX: number = Math.abs(note.PositionAndShape.AbsolutePosition.x - clickPosition.x);
  566. const distY: number = Math.abs(posY - clickPosition.y);
  567. // console.log("note: " + note.sourceNote.Pitch.ToString());
  568. if (distX + distY < closestDist) {
  569. closestNote = note;
  570. closestDist = distX + distY;
  571. }
  572. }
  573. return closestNote;
  574. }
  575. public domToSvg(point: PointF2D): PointF2D {
  576. return this.domToSvgTransform(point, true);
  577. }
  578. public svgToDom(point: PointF2D): PointF2D {
  579. return this.domToSvgTransform(point, false);
  580. }
  581. public svgToOsmd(point: PointF2D): PointF2D {
  582. const pt: PointF2D = new PointF2D(point.x, point.y);
  583. pt.x /= 10; // unitInPixels would need to be imported from VexFlowMusicSheetDrawer
  584. pt.y /= 10;
  585. return pt;
  586. }
  587. // TODO move to VexFlowMusicSheetDrawer? better fit for imports
  588. private domToSvgTransform(point: PointF2D, inverse: boolean): PointF2D {
  589. const svgBackend: any = (this.drawer as any).Backends[0]; // as SvgVexFlowBackend;
  590. // TODO importing SvgVexFlowBackend here causes build problems. Importing VexFlowMusicSheetDrawer seems to be fine, but unnecessary.
  591. // if (!(svgBackend instanceof SvgVexFlowBackend)) {
  592. // return undefined;
  593. // }
  594. const svg: SVGSVGElement = svgBackend.getSvgElement() as SVGSVGElement;
  595. const pt: SVGPoint = svg.createSVGPoint();
  596. pt.x = point.x;
  597. pt.y = point.y;
  598. let transformMatrix: DOMMatrix = svg.getScreenCTM();
  599. if (inverse) {
  600. transformMatrix = transformMatrix.inverse();
  601. }
  602. const sp: SVGPoint = pt.matrixTransform(transformMatrix);
  603. return new PointF2D(sp.x, sp.y);
  604. }
  605. public GetClickableLabel(clickPosition: PointF2D): GraphicalLabel {
  606. const initialSearchAreaX: number = 4;
  607. const initialSearchAreaY: number = 4;
  608. // Prepare search area
  609. const region: BoundingBox = new BoundingBox();
  610. region.BorderLeft = clickPosition.x - initialSearchAreaX;
  611. region.BorderTop = clickPosition.y - initialSearchAreaY;
  612. region.BorderRight = clickPosition.x + initialSearchAreaX;
  613. region.BorderBottom = clickPosition.y + initialSearchAreaY;
  614. region.AbsolutePosition = new PointF2D(0, 0);
  615. for (let idx: number = 0, len: number = this.MusicPages.length; idx < len; ++idx) {
  616. const graphicalMusicPage: GraphicalMusicPage = this.MusicPages[idx];
  617. const entries: GraphicalLabel[] = graphicalMusicPage.PositionAndShape.getObjectsInRegion<GraphicalLabel>(region);
  618. if (entries.length !== 1) {
  619. continue;
  620. } else {
  621. for (let idx2: number = 0, len2: number = entries.length; idx2 < len2; ++idx2) {
  622. const clickedLabel: GraphicalLabel = entries[idx2];
  623. return clickedLabel;
  624. }
  625. }
  626. }
  627. return undefined;
  628. }
  629. public GetPossibleCommentAnchor(clickPosition: PointF2D): SourceStaffEntry {
  630. const entry: GraphicalVoiceEntry = this.GetNearestVoiceEntry(clickPosition);
  631. if (!entry) {
  632. return undefined;
  633. }
  634. return entry.parentStaffEntry.sourceStaffEntry;
  635. }
  636. public GetClickedObjectOfType<T>(positionOnMusicSheet: PointF2D): T {
  637. for (let idx: number = 0, len: number = this.musicPages.length; idx < len; ++idx) {
  638. const page: GraphicalMusicPage = this.musicPages[idx];
  639. const o: Object = page.PositionAndShape.getClickedObjectOfType<T>(positionOnMusicSheet);
  640. if (o) {
  641. return (o as T);
  642. }
  643. }
  644. return undefined;
  645. }
  646. //Comment copied from Bounding box method:
  647. //Generics don't work like this in TS. Casting doesn't filter out objects.
  648. //instanceof doesn't work either with generic types. Hopefully instanceof becomes available at some point, for now we have to do annoyingly
  649. //specific implementations after calling this to filter the objects.
  650. public GetClickedClickable(positionOnMusicSheet: PointF2D): Clickable {
  651. for (let idx: number = 0, len: number = this.musicPages.length; idx < len; ++idx) {
  652. const page: GraphicalMusicPage = this.musicPages[idx];
  653. const o: Object = page.PositionAndShape.getClickedClickable(positionOnMusicSheet);
  654. if (o && o instanceof Clickable) {
  655. return o;
  656. }
  657. }
  658. return undefined;
  659. }
  660. public tryGetTimestampFromPosition(positionOnMusicSheet: PointF2D): Fraction {
  661. const entry: GraphicalStaffEntry = this.GetClickedObjectOfType<GraphicalStaffEntry>(positionOnMusicSheet);
  662. if (!entry) {
  663. return undefined;
  664. }
  665. return entry.getAbsoluteTimestamp();
  666. }
  667. public tryGetClickableLabel(positionOnMusicSheet: PointF2D): GraphicalLabel {
  668. try {
  669. return this.GetClickableLabel(positionOnMusicSheet);
  670. } catch (ex) {
  671. log.info("GraphicalMusicSheet.tryGetClickableObject", "positionOnMusicSheet: " + positionOnMusicSheet, ex);
  672. }
  673. return undefined;
  674. }
  675. public tryGetTimeStampFromPosition(positionOnMusicSheet: PointF2D): Fraction {
  676. try {
  677. const entry: GraphicalVoiceEntry = this.GetNearestVoiceEntry(positionOnMusicSheet);
  678. if (!entry) {
  679. return undefined;
  680. }
  681. return entry.parentStaffEntry.getAbsoluteTimestamp();
  682. } catch (ex) {
  683. log.info(
  684. "GraphicalMusicSheet.tryGetTimeStampFromPosition",
  685. "positionOnMusicSheet: " + positionOnMusicSheet, ex
  686. );
  687. }
  688. return undefined;
  689. }
  690. /**
  691. * Get visible staffentry for the container given by the index.
  692. * @param index
  693. * @returns {GraphicalStaffEntry}
  694. */
  695. public getStaffEntry(index: number): GraphicalStaffEntry {
  696. const container: VerticalGraphicalStaffEntryContainer = this.VerticalGraphicalStaffEntryContainers[index];
  697. let staffEntry: GraphicalStaffEntry = undefined;
  698. try {
  699. for (let idx: number = 0, len: number = container.StaffEntries.length; idx < len; ++idx) {
  700. const entry: GraphicalStaffEntry = container.StaffEntries[idx];
  701. if (!entry || !entry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  702. continue;
  703. }
  704. if (!staffEntry) {
  705. staffEntry = entry;
  706. } else if (entry.PositionAndShape && staffEntry.PositionAndShape) {
  707. if (staffEntry.PositionAndShape.RelativePosition.x > entry.PositionAndShape.RelativePosition.x) {
  708. staffEntry = entry;
  709. }
  710. }
  711. }
  712. } catch (ex) {
  713. log.info("GraphicalMusicSheet.getStaffEntry", ex);
  714. }
  715. return staffEntry;
  716. }
  717. /**
  718. * Returns the index of the closest previous (earlier) vertical container which has at least some visible staff entry, with respect to the given index.
  719. * @param index
  720. * @returns {number}
  721. * @constructor
  722. */
  723. public GetPreviousVisibleContainerIndex(index: number): number {
  724. for (let i: number = index - 1; i >= 0; i--) {
  725. const entries: GraphicalStaffEntry[] = this.verticalGraphicalStaffEntryContainers[i].StaffEntries;
  726. for (let idx: number = 0, len: number = entries.length; idx < len; ++idx) {
  727. const entry: GraphicalStaffEntry = entries[idx];
  728. if (entry && entry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  729. return i;
  730. }
  731. }
  732. }
  733. return -1;
  734. }
  735. /**
  736. * Returns the index of the closest next (later) vertical container which has at least some visible staff entry, with respect to the given index.
  737. * @param index
  738. * @returns {number}
  739. * @constructor
  740. */
  741. public GetNextVisibleContainerIndex(index: number): number {
  742. for (let i: number = index + 1; i < this.verticalGraphicalStaffEntryContainers.length; ++i) {
  743. const entries: GraphicalStaffEntry[] = this.verticalGraphicalStaffEntryContainers[i].StaffEntries;
  744. for (let idx: number = 0, len: number = entries.length; idx < len; ++idx) {
  745. const entry: GraphicalStaffEntry = entries[idx];
  746. if (entry && entry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  747. return i;
  748. }
  749. }
  750. }
  751. return -1;
  752. }
  753. public findClosestLeftStaffEntry(fractionalIndex: number, searchOnlyVisibleEntries: boolean): GraphicalStaffEntry {
  754. let foundEntry: GraphicalStaffEntry = undefined;
  755. let leftIndex: number = Math.floor(fractionalIndex);
  756. leftIndex = Math.min(this.VerticalGraphicalStaffEntryContainers.length - 1, leftIndex);
  757. for (let i: number = leftIndex; i >= 0; i--) {
  758. foundEntry = this.getStaffEntry(i);
  759. if (foundEntry) {
  760. if (searchOnlyVisibleEntries) {
  761. if (foundEntry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  762. return foundEntry;
  763. }
  764. } else {
  765. return foundEntry;
  766. }
  767. }
  768. }
  769. return undefined;
  770. }
  771. public findClosestRightStaffEntry(fractionalIndex: number, returnOnlyVisibleEntries: boolean): GraphicalStaffEntry {
  772. let foundEntry: GraphicalStaffEntry = undefined;
  773. const rightIndex: number = Math.max(0, Math.ceil(fractionalIndex));
  774. for (let i: number = rightIndex; i < this.VerticalGraphicalStaffEntryContainers.length; i++) {
  775. foundEntry = this.getStaffEntry(i);
  776. if (foundEntry) {
  777. if (returnOnlyVisibleEntries) {
  778. if (foundEntry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  779. return foundEntry;
  780. }
  781. } else {
  782. return foundEntry;
  783. }
  784. }
  785. }
  786. return undefined;
  787. }
  788. public calculateCursorLineAtTimestamp(musicTimestamp: Fraction, styleEnum: OutlineAndFillStyleEnum): GraphicalLine {
  789. const result: [number, MusicSystem, any] = this.calculateXPositionFromTimestamp(musicTimestamp);
  790. const xPos: number = result[0];
  791. const correspondingMusicSystem: MusicSystem = result[1];
  792. if (!correspondingMusicSystem || correspondingMusicSystem.StaffLines.length === 0) {
  793. return undefined;
  794. }
  795. const yCoordinate: number = correspondingMusicSystem.PositionAndShape.AbsolutePosition.y;
  796. const height: number = CollectionUtil.last(correspondingMusicSystem.StaffLines).PositionAndShape.RelativePosition.y + 4;
  797. return new GraphicalLine(new PointF2D(xPos, yCoordinate), new PointF2D(xPos, yCoordinate + height), 3, styleEnum);
  798. }
  799. public calculateXPositionFromTimestamp(timeStamp: Fraction): [number, MusicSystem, GraphicalStaffEntry] {
  800. let currentMusicSystem: MusicSystem = undefined;
  801. const fractionalIndex: number = this.GetInterpolatedIndexInVerticalContainers(timeStamp);
  802. const previousStaffEntry: GraphicalStaffEntry = this.findClosestLeftStaffEntry(fractionalIndex, true);
  803. const nextStaffEntry: GraphicalStaffEntry = this.findClosestRightStaffEntry(fractionalIndex, true);
  804. const currentTimeStamp: number = timeStamp.RealValue;
  805. if (!previousStaffEntry && !nextStaffEntry) {
  806. return [0, undefined, undefined];
  807. }
  808. let previousStaffEntryMusicSystem: MusicSystem = undefined;
  809. if (previousStaffEntry) {
  810. // TODO sometimes one of these ParentStaffLine is undefined, either fix this or handle it here
  811. previousStaffEntryMusicSystem = previousStaffEntry.parentMeasure.ParentStaffLine?.ParentMusicSystem;
  812. } else {
  813. previousStaffEntryMusicSystem = nextStaffEntry.parentMeasure.ParentStaffLine?.ParentMusicSystem;
  814. }
  815. let nextStaffEntryMusicSystem: MusicSystem = undefined;
  816. if (nextStaffEntry) {
  817. nextStaffEntryMusicSystem = nextStaffEntry.parentMeasure.ParentStaffLine?.ParentMusicSystem;
  818. } else {
  819. nextStaffEntryMusicSystem = previousStaffEntry.parentMeasure.ParentStaffLine?.ParentMusicSystem;
  820. }
  821. let prevMeasure: SourceMeasure;
  822. if (previousStaffEntryMusicSystem === nextStaffEntryMusicSystem) {
  823. currentMusicSystem = previousStaffEntryMusicSystem;
  824. let fraction: number;
  825. let previousStaffEntryPositionX: number;
  826. let nextStaffEntryPositionX: number;
  827. if (!previousStaffEntry) {
  828. previousStaffEntryPositionX = nextStaffEntryPositionX = nextStaffEntry.PositionAndShape.AbsolutePosition.x;
  829. fraction = 0;
  830. } else if (!nextStaffEntry) {
  831. previousStaffEntryPositionX = previousStaffEntry.PositionAndShape.AbsolutePosition.x;
  832. nextStaffEntryPositionX = currentMusicSystem.GetRightBorderAbsoluteXPosition();
  833. prevMeasure = previousStaffEntry.parentMeasure.parentSourceMeasure;
  834. fraction = (currentTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue) / (
  835. Fraction.plus(prevMeasure.AbsoluteTimestamp, prevMeasure.Duration).RealValue - previousStaffEntry.getAbsoluteTimestamp().RealValue);
  836. } else {
  837. previousStaffEntryPositionX = previousStaffEntry.PositionAndShape.AbsolutePosition.x;
  838. nextStaffEntryPositionX = nextStaffEntry.PositionAndShape.AbsolutePosition.x;
  839. prevMeasure = previousStaffEntry.parentMeasure.parentSourceMeasure;
  840. if (previousStaffEntry === nextStaffEntry) {
  841. fraction = 0;
  842. } else {
  843. fraction = (currentTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue) /
  844. (nextStaffEntry.getAbsoluteTimestamp().RealValue - previousStaffEntry.getAbsoluteTimestamp().RealValue);
  845. }
  846. }
  847. fraction = Math.min(1, Math.max(0, fraction));
  848. let limitX: number = nextStaffEntryPositionX;
  849. if (prevMeasure?.Rules.LimitCursorPositionToCurrentMeasure) {
  850. limitX = Math.min(previousStaffEntry.parentMeasure.PositionAndShape.AbsolutePosition.x +
  851. previousStaffEntry.parentMeasure.PositionAndShape.BorderRight, limitX);
  852. }
  853. const interpolatedXPosition: number = previousStaffEntryPositionX + fraction * (limitX - previousStaffEntryPositionX);
  854. return [interpolatedXPosition, currentMusicSystem, previousStaffEntry];
  855. } else {
  856. const nextSystemLeftBorderTimeStamp: number = nextStaffEntry.parentMeasure.parentSourceMeasure.AbsoluteTimestamp.RealValue;
  857. let fraction: number;
  858. let interpolatedXPosition: number;
  859. if (currentTimeStamp < nextSystemLeftBorderTimeStamp) {
  860. currentMusicSystem = previousStaffEntryMusicSystem;
  861. const previousStaffEntryPositionX: number = previousStaffEntry.PositionAndShape.AbsolutePosition.x;
  862. const previousSystemRightBorderX: number = currentMusicSystem.GetRightBorderAbsoluteXPosition();
  863. fraction = (currentTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue) /
  864. (nextSystemLeftBorderTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue);
  865. fraction = Math.min(1, Math.max(0, fraction));
  866. interpolatedXPosition = previousStaffEntryPositionX + fraction * (previousSystemRightBorderX - previousStaffEntryPositionX);
  867. } else {
  868. currentMusicSystem = nextStaffEntryMusicSystem;
  869. const nextStaffEntryPositionX: number = nextStaffEntry.PositionAndShape.AbsolutePosition.x;
  870. const nextSystemLeftBorderX: number = currentMusicSystem.GetLeftBorderAbsoluteXPosition();
  871. fraction = (currentTimeStamp - nextSystemLeftBorderTimeStamp) /
  872. (nextStaffEntry.getAbsoluteTimestamp().RealValue - nextSystemLeftBorderTimeStamp);
  873. fraction = Math.min(1, Math.max(0, fraction));
  874. interpolatedXPosition = nextSystemLeftBorderX + fraction * (nextStaffEntryPositionX - nextSystemLeftBorderX);
  875. }
  876. return [interpolatedXPosition, currentMusicSystem, previousStaffEntry];
  877. }
  878. }
  879. public calculateCursorPoints(xPos: number, correspondingSystem: MusicSystem): [PointF2D, PointF2D] {
  880. if (correspondingSystem === undefined || correspondingSystem.StaffLines.length === 0) {
  881. return [new PointF2D(), new PointF2D()];
  882. }
  883. const yCoordinate: number = correspondingSystem.PositionAndShape.AbsolutePosition.y;
  884. const lastStaffLine: StaffLine = correspondingSystem.StaffLines.last();
  885. const height: number = lastStaffLine.PositionAndShape.RelativePosition.y + lastStaffLine.StaffHeight;
  886. return [new PointF2D(xPos, yCoordinate), new PointF2D(xPos, yCoordinate + height)];
  887. }
  888. public GetNumberOfVisibleInstruments(): number {
  889. let visibleInstrumentCount: number = 0;
  890. for (let idx: number = 0, len: number = this.musicSheet.Instruments.length; idx < len; ++idx) {
  891. const instrument: Instrument = this.musicSheet.Instruments[idx];
  892. if (instrument.Visible === true) {
  893. visibleInstrumentCount++;
  894. }
  895. }
  896. return visibleInstrumentCount;
  897. }
  898. public GetNumberOfFollowedInstruments(): number {
  899. let followedInstrumentCount: number = 0;
  900. for (let idx: number = 0, len: number = this.musicSheet.Instruments.length; idx < len; ++idx) {
  901. const instrument: Instrument = this.musicSheet.Instruments[idx];
  902. if (instrument.Following === true) {
  903. followedInstrumentCount++;
  904. }
  905. }
  906. return followedInstrumentCount;
  907. }
  908. /*public GetGraphicalFromSourceMeasure(sourceMeasure: SourceMeasure): GraphicalMeasure[] {
  909. return this.sourceToGraphicalMeasureLinks.getValue(sourceMeasure); // TODO gets wrong measure because sourceMeasure is not a valid key
  910. }*/
  911. public GetGraphicalFromSourceStaffEntry(sourceStaffEntry: SourceStaffEntry): GraphicalStaffEntry {
  912. if (!sourceStaffEntry.VerticalContainerParent.ParentMeasure?.VerticalMeasureList) {
  913. return undefined;
  914. }
  915. const graphicalMeasure: GraphicalMeasure = sourceStaffEntry.VerticalContainerParent.ParentMeasure.VerticalMeasureList
  916. [sourceStaffEntry.ParentStaff.idInMusicSheet];
  917. return graphicalMeasure?.findGraphicalStaffEntryFromTimestamp(sourceStaffEntry.Timestamp);
  918. }
  919. private CalculateDistance(pt1: PointF2D, pt2: PointF2D): number {
  920. const deltaX: number = pt1.x - pt2.x;
  921. const deltaY: number = pt1.y - pt2.y;
  922. return (deltaX * deltaX) + (deltaY * deltaY);
  923. }
  924. /**
  925. * Return the longest StaffEntry duration from a GraphicalVerticalContainer.
  926. * @param index the index of the vertical container
  927. * @returns {Fraction}
  928. */
  929. private getLongestStaffEntryDuration(index: number): Fraction {
  930. let maxLength: Fraction = new Fraction(0, 1);
  931. for (const graphicalStaffEntry of this.verticalGraphicalStaffEntryContainers[index].StaffEntries) {
  932. if (!graphicalStaffEntry) {
  933. continue;
  934. }
  935. const maxLengthInStaffEntry: Fraction = graphicalStaffEntry.findStaffEntryMaxNoteLength();
  936. if (maxLength.lt(maxLengthInStaffEntry)) {
  937. maxLength = maxLengthInStaffEntry;
  938. }
  939. }
  940. return maxLength;
  941. }
  942. }
  943. export class SystemImageProperties {
  944. public positionInPixels: PointF2D;
  945. public systemImageId: number;
  946. public system: MusicSystem;
  947. }