GraphicalMusicSheet.ts 46 KB

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