GraphicalMusicSheet.ts 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  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 { Dictionary } from "typescript-collections"; // unused for now
  22. import {CollectionUtil} from "../../Util/CollectionUtil";
  23. import {SelectionStartSymbol} from "./SelectionStartSymbol";
  24. import {SelectionEndSymbol} from "./SelectionEndSymbol";
  25. import {OutlineAndFillStyleEnum} from "./DrawingEnums";
  26. /**
  27. * The graphical counterpart of a [[MusicSheet]]
  28. */
  29. export class GraphicalMusicSheet {
  30. constructor(musicSheet: MusicSheet, calculator: MusicSheetCalculator) {
  31. this.musicSheet = musicSheet;
  32. this.numberOfStaves = this.musicSheet.Staves.length;
  33. this.calculator = calculator;
  34. this.calculator.initialize(this);
  35. }
  36. private musicSheet: MusicSheet;
  37. //private fontInfo: FontInfo = FontInfo.Info;
  38. private calculator: MusicSheetCalculator;
  39. private musicPages: GraphicalMusicPage[] = [];
  40. /** measures (i,j) where i is the measure number and j the staff index (e.g. staff indices 0, 1 for two piano parts) */
  41. private measureList: GraphicalMeasure[][] = [];
  42. private verticalGraphicalStaffEntryContainers: VerticalGraphicalStaffEntryContainer[] = [];
  43. private title: GraphicalLabel;
  44. private subtitle: GraphicalLabel;
  45. private composer: GraphicalLabel;
  46. private lyricist: GraphicalLabel;
  47. private cursors: GraphicalLine[] = [];
  48. private selectionStartSymbol: SelectionStartSymbol;
  49. private selectionEndSymbol: SelectionEndSymbol;
  50. private minAllowedSystemWidth: number;
  51. //private systemImages: Dictionary<MusicSystem, SystemImageProperties> = new Dictionary<MusicSystem, SystemImageProperties>();
  52. private numberOfStaves: number;
  53. private leadSheet: boolean = false;
  54. public get ParentMusicSheet(): MusicSheet {
  55. return this.musicSheet;
  56. }
  57. public get GetCalculator(): MusicSheetCalculator {
  58. return this.calculator;
  59. }
  60. public get MusicPages(): GraphicalMusicPage[] {
  61. return this.musicPages;
  62. }
  63. public set MusicPages(value: GraphicalMusicPage[]) {
  64. this.musicPages = value;
  65. }
  66. //public get FontInfo(): FontInfo {
  67. // return this.fontInfo;
  68. //}
  69. public get MeasureList(): GraphicalMeasure[][] {
  70. return this.measureList;
  71. }
  72. public set MeasureList(value: GraphicalMeasure[][]) {
  73. this.measureList = value;
  74. }
  75. public get VerticalGraphicalStaffEntryContainers(): VerticalGraphicalStaffEntryContainer[] {
  76. return this.verticalGraphicalStaffEntryContainers;
  77. }
  78. public set VerticalGraphicalStaffEntryContainers(value: VerticalGraphicalStaffEntryContainer[]) {
  79. this.verticalGraphicalStaffEntryContainers = value;
  80. }
  81. public get Title(): GraphicalLabel {
  82. return this.title;
  83. }
  84. public set Title(value: GraphicalLabel) {
  85. this.title = value;
  86. }
  87. public get Subtitle(): GraphicalLabel {
  88. return this.subtitle;
  89. }
  90. public set Subtitle(value: GraphicalLabel) {
  91. this.subtitle = value;
  92. }
  93. public get Composer(): GraphicalLabel {
  94. return this.composer;
  95. }
  96. public set Composer(value: GraphicalLabel) {
  97. this.composer = value;
  98. }
  99. public get Lyricist(): GraphicalLabel {
  100. return this.lyricist;
  101. }
  102. public set Lyricist(value: GraphicalLabel) {
  103. this.lyricist = value;
  104. }
  105. public get Cursors(): GraphicalLine[] {
  106. return this.cursors;
  107. }
  108. public get SelectionStartSymbol(): SelectionStartSymbol {
  109. return this.selectionStartSymbol;
  110. }
  111. public get SelectionEndSymbol(): SelectionEndSymbol {
  112. return this.selectionEndSymbol;
  113. }
  114. public get MinAllowedSystemWidth(): number {
  115. return this.minAllowedSystemWidth;
  116. }
  117. public set MinAllowedSystemWidth(value: number) {
  118. this.minAllowedSystemWidth = value;
  119. }
  120. // public get SystemImages(): Dictionary<MusicSystem, SystemImageProperties> {
  121. // return this.systemImages;
  122. // }
  123. public get NumberOfStaves(): number {
  124. return this.numberOfStaves;
  125. }
  126. public get LeadSheet(): boolean {
  127. return this.leadSheet;
  128. }
  129. public set LeadSheet(value: boolean) {
  130. this.leadSheet = value;
  131. }
  132. /**
  133. * Calculate the Absolute Positions from the Relative Positions.
  134. * @param graphicalMusicSheet
  135. */
  136. public static transformRelativeToAbsolutePosition(graphicalMusicSheet: GraphicalMusicSheet): void {
  137. for (let i: number = 0; i < graphicalMusicSheet.MusicPages.length; i++) {
  138. const pageAbsolute: PointF2D = graphicalMusicSheet.MusicPages[i].setMusicPageAbsolutePosition(i, graphicalMusicSheet.ParentMusicSheet.Rules);
  139. const page: GraphicalMusicPage = graphicalMusicSheet.MusicPages[i];
  140. page.PositionAndShape.calculateAbsolutePositionsRecursive(pageAbsolute.x, pageAbsolute.y);
  141. }
  142. }
  143. public Initialize(): void {
  144. this.verticalGraphicalStaffEntryContainers = [];
  145. this.musicPages = [];
  146. this.measureList = [];
  147. }
  148. public reCalculate(): void {
  149. this.calculator.calculate();
  150. }
  151. public prepare(): void {
  152. this.calculator.prepareGraphicalMusicSheet();
  153. }
  154. public EnforceRedrawOfMusicSystems(): void {
  155. for (let idx: number = 0, len: number = this.musicPages.length; idx < len; ++idx) {
  156. const graphicalMusicPage: GraphicalMusicPage = this.musicPages[idx];
  157. for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
  158. const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
  159. musicSystem.needsToBeRedrawn = true;
  160. }
  161. }
  162. }
  163. public getClickedObject<T>(positionOnMusicSheet: PointF2D): T {
  164. for (let idx: number = 0, len: number = this.MusicPages.length; idx < len; ++idx) {
  165. const graphicalMusicPage: GraphicalMusicPage = this.MusicPages[idx];
  166. return graphicalMusicPage.PositionAndShape.getClickedObjectOfType<T>(positionOnMusicSheet);
  167. }
  168. return undefined;
  169. }
  170. /**
  171. * Search the MeasureList for a certain GraphicalStaffEntry with the given SourceStaffEntry,
  172. * at a certain verticalIndex (eg a corresponding Staff), starting at a specific horizontalIndex (eg specific GraphicalMeasure).
  173. * @param staffIndex
  174. * @param measureIndex
  175. * @param sourceStaffEntry
  176. * @returns {any}
  177. */
  178. public findGraphicalStaffEntryFromMeasureList(staffIndex: number, measureIndex: number, sourceStaffEntry: SourceStaffEntry): GraphicalStaffEntry {
  179. for (let i: number = measureIndex; i < this.measureList.length; i++) {
  180. const graphicalMeasure: GraphicalMeasure = this.measureList[i][staffIndex];
  181. if (!graphicalMeasure) {
  182. continue;
  183. }
  184. for (let idx: number = 0, len: number = graphicalMeasure.staffEntries.length; idx < len; ++idx) {
  185. const graphicalStaffEntry: GraphicalStaffEntry = graphicalMeasure.staffEntries[idx];
  186. if (graphicalStaffEntry.sourceStaffEntry === sourceStaffEntry) {
  187. return graphicalStaffEntry;
  188. }
  189. }
  190. }
  191. return undefined;
  192. }
  193. /**
  194. * Return the next (to the right) not null GraphicalStaffEntry from a given Index.
  195. * @param staffIndex
  196. * @param measureIndex
  197. * @param graphicalStaffEntry
  198. * @returns {any}
  199. */
  200. public findNextGraphicalStaffEntry(staffIndex: number, measureIndex: number, graphicalStaffEntry: GraphicalStaffEntry): GraphicalStaffEntry {
  201. const graphicalMeasure: GraphicalMeasure = graphicalStaffEntry.parentMeasure;
  202. const graphicalStaffEntryIndex: number = graphicalMeasure.staffEntries.indexOf(graphicalStaffEntry);
  203. if (graphicalStaffEntryIndex < graphicalMeasure.staffEntries.length - 1) {
  204. return graphicalMeasure.staffEntries[graphicalStaffEntryIndex + 1];
  205. } else if (measureIndex < this.measureList.length - 1) {
  206. const nextMeasure: GraphicalMeasure = this.measureList[measureIndex + 1][staffIndex];
  207. if (nextMeasure.staffEntries.length > 0) {
  208. return nextMeasure.staffEntries[0];
  209. }
  210. }
  211. return undefined;
  212. }
  213. public getFirstVisibleMeasuresListFromIndices(start: number, end: number): GraphicalMeasure[] {
  214. const graphicalMeasures: GraphicalMeasure[] = [];
  215. const numberOfStaves: number = this.measureList[0].length;
  216. for (let i: number = start; i <= end; i++) {
  217. for (let j: number = 0; j < numberOfStaves; j++) {
  218. if (this.measureList[i][j].isVisible()) {
  219. graphicalMeasures.push(this.measureList[i][j]);
  220. break;
  221. }
  222. }
  223. }
  224. return graphicalMeasures;
  225. }
  226. public orderMeasuresByStaffLine(measures: GraphicalMeasure[]): GraphicalMeasure[][] {
  227. const orderedMeasures: GraphicalMeasure[][] = [];
  228. let mList: GraphicalMeasure[] = [];
  229. orderedMeasures.push(mList);
  230. for (let i: number = 0; i < measures.length; i++) {
  231. if (i === 0) {
  232. mList.push(measures[0]);
  233. } else {
  234. if (measures[i].ParentStaffLine === measures[i - 1].ParentStaffLine) {
  235. mList.push(measures[i]);
  236. } else {
  237. if (orderedMeasures.indexOf(mList) === -1) {
  238. orderedMeasures.push(mList);
  239. }
  240. mList = [];
  241. orderedMeasures.push(mList);
  242. mList.push(measures[i]);
  243. }
  244. }
  245. }
  246. return orderedMeasures;
  247. }
  248. /**
  249. * Return the active Clefs at the start of the first SourceMeasure.
  250. * @returns {ClefInstruction[]}
  251. */
  252. public initializeActiveClefs(): ClefInstruction[] {
  253. const activeClefs: ClefInstruction[] = [];
  254. const firstSourceMeasure: SourceMeasure = this.musicSheet.getFirstSourceMeasure();
  255. if (firstSourceMeasure) {
  256. for (let i: number = 0; i < firstSourceMeasure.CompleteNumberOfStaves; i++) {
  257. let clef: ClefInstruction = new ClefInstruction();
  258. if (firstSourceMeasure.FirstInstructionsStaffEntries[i]) {
  259. for (let idx: number = 0, len: number = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions.length; idx < len; ++idx) {
  260. const abstractNotationInstruction: AbstractNotationInstruction = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions[idx];
  261. if (abstractNotationInstruction instanceof ClefInstruction) {
  262. clef = <ClefInstruction>abstractNotationInstruction;
  263. }
  264. }
  265. }
  266. activeClefs.push(clef);
  267. }
  268. }
  269. return activeClefs;
  270. }
  271. public GetMainKey(): KeyInstruction {
  272. const firstSourceMeasure: SourceMeasure = this.musicSheet.getFirstSourceMeasure();
  273. if (firstSourceMeasure) {
  274. for (let i: number = 0; i < firstSourceMeasure.CompleteNumberOfStaves; i++) {
  275. for (let idx: number = 0, len: number = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions.length; idx < len; ++idx) {
  276. const abstractNotationInstruction: AbstractNotationInstruction = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions[idx];
  277. if (abstractNotationInstruction instanceof KeyInstruction) {
  278. return <KeyInstruction>abstractNotationInstruction;
  279. }
  280. }
  281. }
  282. }
  283. return undefined;
  284. }
  285. /**
  286. * Create the VerticalContainer and adds it to the List at the correct Timestamp position.
  287. * @param timestamp
  288. * @returns {any}
  289. */
  290. public getOrCreateVerticalContainer(timestamp: Fraction): VerticalGraphicalStaffEntryContainer {
  291. if (this.verticalGraphicalStaffEntryContainers.length === 0 ||
  292. (CollectionUtil.getLastElement(this.verticalGraphicalStaffEntryContainers).AbsoluteTimestamp).lt(timestamp)) {
  293. const verticalGraphicalStaffEntryContainer: VerticalGraphicalStaffEntryContainer =
  294. new VerticalGraphicalStaffEntryContainer(this.numberOfStaves, timestamp);
  295. this.verticalGraphicalStaffEntryContainers.push(verticalGraphicalStaffEntryContainer);
  296. return verticalGraphicalStaffEntryContainer;
  297. }
  298. for (let i: number = this.verticalGraphicalStaffEntryContainers.length - 1; i >= 0; i--) {
  299. if (this.verticalGraphicalStaffEntryContainers[i].AbsoluteTimestamp.lt(timestamp)) {
  300. const verticalGraphicalStaffEntryContainer: VerticalGraphicalStaffEntryContainer =
  301. new VerticalGraphicalStaffEntryContainer(this.numberOfStaves, timestamp);
  302. this.verticalGraphicalStaffEntryContainers.splice(i + 1, 0, verticalGraphicalStaffEntryContainer);
  303. return verticalGraphicalStaffEntryContainer;
  304. }
  305. if (this.verticalGraphicalStaffEntryContainers[i].AbsoluteTimestamp.Equals(timestamp)) {
  306. return this.verticalGraphicalStaffEntryContainers[i];
  307. }
  308. }
  309. return undefined;
  310. }
  311. /**
  312. * Does a binary search on the container list and returns the VerticalContainer with the given Timestamp.
  313. * The search begins at startIndex, if given.
  314. * If the timestamp cannot be found, null is returned.
  315. * @param timestamp - The timestamp for which the container shall be found.
  316. * @param startIndex - The index from which the search starts in the container list.
  317. * @returns {any}
  318. * @constructor
  319. */
  320. public GetVerticalContainerFromTimestamp(timestamp: Fraction, startIndex: number = 0): VerticalGraphicalStaffEntryContainer {
  321. const index: number = CollectionUtil.binarySearch(this.verticalGraphicalStaffEntryContainers,
  322. new VerticalGraphicalStaffEntryContainer(0, timestamp),
  323. VerticalGraphicalStaffEntryContainer.compareByTimestamp,
  324. startIndex);
  325. if (index >= 0) {
  326. return this.verticalGraphicalStaffEntryContainers[index];
  327. }
  328. return undefined;
  329. }
  330. /**
  331. * Perform a binary search for the absolute given Timestamp in all the GraphicalVerticalContainers.
  332. * @param musicTimestamp
  333. * @returns {number}
  334. * @constructor
  335. */
  336. public GetInterpolatedIndexInVerticalContainers(musicTimestamp: Fraction): number {
  337. const containers: VerticalGraphicalStaffEntryContainer[] = this.verticalGraphicalStaffEntryContainers;
  338. let leftIndex: number = 0;
  339. let rightIndex: number = containers.length - 1;
  340. let foundIndex: number;
  341. let leftTS: Fraction = undefined;
  342. let rightTS: Fraction = undefined;
  343. if (musicTimestamp.lte(containers[containers.length - 1].AbsoluteTimestamp)) {
  344. while (rightIndex - leftIndex > 1) {
  345. const middleIndex: number = Math.floor((rightIndex + leftIndex) / 2);
  346. if (containers[leftIndex].AbsoluteTimestamp.Equals(musicTimestamp)) {
  347. rightIndex = leftIndex;
  348. break;
  349. } else if (containers[rightIndex].AbsoluteTimestamp.Equals(musicTimestamp)) {
  350. leftIndex = rightIndex;
  351. break;
  352. } else if (containers[middleIndex].AbsoluteTimestamp.Equals(musicTimestamp)) {
  353. return this.verticalGraphicalStaffEntryContainers.indexOf(containers[middleIndex]);
  354. } else if (musicTimestamp.lt(containers[middleIndex].AbsoluteTimestamp)) {
  355. rightIndex = middleIndex;
  356. } else {
  357. leftIndex = middleIndex;
  358. }
  359. }
  360. // no interpolation needed
  361. if (leftIndex === rightIndex) {
  362. return this.verticalGraphicalStaffEntryContainers.indexOf(containers[leftIndex]);
  363. }
  364. leftTS = containers[leftIndex].AbsoluteTimestamp;
  365. rightTS = containers[rightIndex].AbsoluteTimestamp;
  366. } else {
  367. leftTS = containers[containers.length - 1].AbsoluteTimestamp;
  368. rightTS = Fraction.plus(this.getLongestStaffEntryDuration(containers.length - 1), leftTS);
  369. rightIndex = containers.length;
  370. }
  371. const diff: number = rightTS.RealValue - leftTS.RealValue;
  372. const diffTS: number = rightTS.RealValue - musicTimestamp.RealValue;
  373. // estimate the interpolated index
  374. foundIndex = rightIndex - (diffTS / diff);
  375. return Math.min(foundIndex, this.verticalGraphicalStaffEntryContainers.length);
  376. }
  377. /**
  378. * Get a List with the indices of all the visible GraphicalMeasures and calculates their
  379. * corresponding indices in the first SourceMeasure, taking into account Instruments with multiple Staves.
  380. * @param visibleMeasures
  381. * @returns {number[]}
  382. */
  383. public getVisibleStavesIndicesFromSourceMeasure(visibleMeasures: GraphicalMeasure[]): number[] {
  384. const visibleInstruments: Instrument[] = [];
  385. const visibleStavesIndices: number[] = [];
  386. for (let idx: number = 0, len: number = visibleMeasures.length; idx < len; ++idx) {
  387. const graphicalMeasure: GraphicalMeasure = visibleMeasures[idx];
  388. const instrument: Instrument = graphicalMeasure.ParentStaff.ParentInstrument;
  389. if (visibleInstruments.indexOf(instrument) === -1) {
  390. visibleInstruments.push(instrument);
  391. }
  392. }
  393. for (let idx: number = 0, len: number = visibleInstruments.length; idx < len; ++idx) {
  394. const instrument: Instrument = visibleInstruments[idx];
  395. const index: number = this.musicSheet.getGlobalStaffIndexOfFirstStaff(instrument);
  396. for (let j: number = 0; j < instrument.Staves.length; j++) {
  397. visibleStavesIndices.push(index + j);
  398. }
  399. }
  400. return visibleStavesIndices;
  401. }
  402. /**
  403. * Returns the GraphicalMeasure with the given SourceMeasure as Parent at the given staff index.
  404. * @param sourceMeasure
  405. * @param staffIndex
  406. * @returns {any}
  407. */
  408. public getGraphicalMeasureFromSourceMeasureAndIndex(sourceMeasure: SourceMeasure, staffIndex: number): GraphicalMeasure {
  409. for (let i: number = 0; i < this.measureList.length; i++) {
  410. if (this.measureList[i][0].parentSourceMeasure === sourceMeasure) {
  411. return this.measureList[i][staffIndex];
  412. }
  413. }
  414. return undefined;
  415. }
  416. public getLastGraphicalMeasureFromIndex(staffIndex: number, lastRendered: boolean = true): GraphicalMeasure {
  417. let measureIndex: number = this.measureList.length - 1;
  418. if (lastRendered) {
  419. measureIndex = Math.min(measureIndex, this.musicSheet.Rules.MaxMeasureToDrawIndex);
  420. }
  421. return this.measureList[measureIndex][staffIndex];
  422. }
  423. public getMeasureIndex(graphicalMeasure: GraphicalMeasure, measureIndex: number, inListIndex: number): boolean {
  424. measureIndex = 0;
  425. inListIndex = 0;
  426. for (; measureIndex < this.measureList.length; measureIndex++) {
  427. for (let idx: number = 0, len: number = this.measureList[measureIndex].length; idx < len; ++idx) {
  428. const measure: GraphicalMeasure = this.measureList[measureIndex][idx];
  429. if (measure === graphicalMeasure) {
  430. return true;
  431. }
  432. }
  433. }
  434. return false;
  435. }
  436. public GetNearestNote(clickPosition: PointF2D, maxClickDist: PointF2D): GraphicalNote {
  437. const initialSearchArea: number = 10;
  438. const foundNotes: GraphicalNote[] = [];
  439. // Prepare search area
  440. const region: BoundingBox = new BoundingBox();
  441. region.BorderLeft = clickPosition.x - initialSearchArea;
  442. region.BorderTop = clickPosition.y - initialSearchArea;
  443. region.BorderRight = clickPosition.x + initialSearchArea;
  444. region.BorderBottom = clickPosition.y + initialSearchArea;
  445. region.AbsolutePosition = new PointF2D(0, 0);
  446. // Search for StaffEntries in region
  447. for (let idx: number = 0, len: number = this.MusicPages.length; idx < len; ++idx) {
  448. const graphicalMusicPage: GraphicalMusicPage = this.MusicPages[idx];
  449. const entries: GraphicalNote[] = graphicalMusicPage.PositionAndShape.getObjectsInRegion<GraphicalNote>(region);
  450. //let entriesArr: GraphicalNote[] = __as__<GraphicalNote[]>(entries, GraphicalNote[]) ? ? entries;
  451. if (!entries) {
  452. continue;
  453. } else {
  454. for (let idx2: number = 0, len2: number = entries.length; idx2 < len2; ++idx2) {
  455. const note: GraphicalNote = entries[idx2];
  456. if (Math.abs(note.PositionAndShape.AbsolutePosition.x - clickPosition.x) < maxClickDist.x
  457. && Math.abs(note.PositionAndShape.AbsolutePosition.y - clickPosition.y) < maxClickDist.y) {
  458. foundNotes.push(note);
  459. }
  460. }
  461. }
  462. }
  463. // Get closest entry
  464. let closest: GraphicalNote = undefined;
  465. for (let idx: number = 0, len: number = foundNotes.length; idx < len; ++idx) {
  466. const note: GraphicalNote = foundNotes[idx];
  467. if (closest === undefined) {
  468. closest = note;
  469. } else {
  470. if (!note.parentVoiceEntry.parentStaffEntry.relInMeasureTimestamp) {
  471. continue;
  472. }
  473. const deltaNew: number = this.CalculateDistance(note.PositionAndShape.AbsolutePosition, clickPosition);
  474. const deltaOld: number = this.CalculateDistance(closest.PositionAndShape.AbsolutePosition, clickPosition);
  475. if (deltaNew < deltaOld) {
  476. closest = note;
  477. }
  478. }
  479. }
  480. if (closest) {
  481. return closest;
  482. }
  483. // TODO No staff entry was found. Feedback?
  484. // throw new ArgumentException("No staff entry found");
  485. return undefined;
  486. }
  487. public GetClickableLabel(clickPosition: PointF2D): GraphicalLabel {
  488. const initialSearchAreaX: number = 4;
  489. const initialSearchAreaY: number = 4;
  490. // Prepare search area
  491. const region: BoundingBox = new BoundingBox();
  492. region.BorderLeft = clickPosition.x - initialSearchAreaX;
  493. region.BorderTop = clickPosition.y - initialSearchAreaY;
  494. region.BorderRight = clickPosition.x + initialSearchAreaX;
  495. region.BorderBottom = clickPosition.y + initialSearchAreaY;
  496. region.AbsolutePosition = new PointF2D(0, 0);
  497. for (let idx: number = 0, len: number = this.MusicPages.length; idx < len; ++idx) {
  498. const graphicalMusicPage: GraphicalMusicPage = this.MusicPages[idx];
  499. const entries: GraphicalLabel[] = graphicalMusicPage.PositionAndShape.getObjectsInRegion<GraphicalLabel>(region);
  500. if (entries.length !== 1) {
  501. continue;
  502. } else {
  503. for (let idx2: number = 0, len2: number = entries.length; idx2 < len2; ++idx2) {
  504. const clickedLabel: GraphicalLabel = entries[idx2];
  505. return clickedLabel;
  506. }
  507. }
  508. }
  509. return undefined;
  510. }
  511. public GetNearestStaffEntry(clickPosition: PointF2D): GraphicalStaffEntry {
  512. const initialSearchArea: number = 10;
  513. const foundEntries: GraphicalStaffEntry[] = [];
  514. // Prepare search area
  515. const region: BoundingBox = new BoundingBox(undefined);
  516. region.BorderLeft = clickPosition.x - initialSearchArea;
  517. region.BorderTop = clickPosition.y - initialSearchArea;
  518. region.BorderRight = clickPosition.x + initialSearchArea;
  519. region.BorderBottom = clickPosition.y + initialSearchArea;
  520. region.AbsolutePosition = new PointF2D(0, 0);
  521. // Search for StaffEntries in region
  522. for (let idx: number = 0, len: number = this.MusicPages.length; idx < len; ++idx) {
  523. const graphicalMusicPage: GraphicalMusicPage = this.MusicPages[idx];
  524. const entries: GraphicalStaffEntry[] = graphicalMusicPage.PositionAndShape.getObjectsInRegion<GraphicalStaffEntry>(region, false);
  525. if (!entries || entries.length === 0) {
  526. continue;
  527. } else {
  528. for (let idx2: number = 0, len2: number = entries.length; idx2 < len2; ++idx2) {
  529. const gse: GraphicalStaffEntry = entries[idx2];
  530. foundEntries.push(gse);
  531. }
  532. }
  533. }
  534. // Get closest entry
  535. let closest: GraphicalStaffEntry = undefined;
  536. for (let idx: number = 0, len: number = foundEntries.length; idx < len; ++idx) {
  537. const gse: GraphicalStaffEntry = foundEntries[idx];
  538. if (closest === undefined) {
  539. closest = gse;
  540. } else {
  541. if (!gse.relInMeasureTimestamp) {
  542. continue;
  543. }
  544. const deltaNew: number = this.CalculateDistance(gse.PositionAndShape.AbsolutePosition, clickPosition);
  545. const deltaOld: number = this.CalculateDistance(closest.PositionAndShape.AbsolutePosition, clickPosition);
  546. if (deltaNew < deltaOld) {
  547. closest = gse;
  548. }
  549. }
  550. }
  551. if (closest) {
  552. return closest;
  553. }
  554. // TODO No staff entry was found. Feedback?
  555. // throw new ArgumentException("No staff entry found");
  556. return undefined;
  557. }
  558. public GetPossibleCommentAnchor(clickPosition: PointF2D): SourceStaffEntry {
  559. const entry: GraphicalStaffEntry = this.GetNearestStaffEntry(clickPosition);
  560. if (!entry) {
  561. return undefined;
  562. }
  563. return entry.sourceStaffEntry;
  564. }
  565. public getClickedObjectOfType<T>(positionOnMusicSheet: PointF2D): T {
  566. for (let idx: number = 0, len: number = this.musicPages.length; idx < len; ++idx) {
  567. const page: GraphicalMusicPage = this.musicPages[idx];
  568. const o: Object = page.PositionAndShape.getClickedObjectOfType<T>(positionOnMusicSheet);
  569. if (o) {
  570. return (o as T);
  571. }
  572. }
  573. return undefined;
  574. }
  575. public tryGetTimestampFromPosition(positionOnMusicSheet: PointF2D): Fraction {
  576. const entry: GraphicalStaffEntry = this.getClickedObjectOfType<GraphicalStaffEntry>(positionOnMusicSheet);
  577. if (!entry) {
  578. return undefined;
  579. }
  580. return entry.getAbsoluteTimestamp();
  581. }
  582. public tryGetClickableLabel(positionOnMusicSheet: PointF2D): GraphicalLabel {
  583. try {
  584. return this.GetClickableLabel(positionOnMusicSheet);
  585. } catch (ex) {
  586. log.info("GraphicalMusicSheet.tryGetClickableObject", "positionOnMusicSheet: " + positionOnMusicSheet, ex);
  587. }
  588. return undefined;
  589. }
  590. public tryGetTimeStampFromPosition(positionOnMusicSheet: PointF2D): Fraction {
  591. try {
  592. const entry: GraphicalStaffEntry = this.GetNearestStaffEntry(positionOnMusicSheet);
  593. if (!entry) {
  594. return undefined;
  595. }
  596. return entry.getAbsoluteTimestamp();
  597. } catch (ex) {
  598. log.info(
  599. "GraphicalMusicSheet.tryGetTimeStampFromPosition",
  600. "positionOnMusicSheet: " + positionOnMusicSheet, ex
  601. );
  602. }
  603. return undefined;
  604. }
  605. /**
  606. * Get visible staffentry for the container given by the index.
  607. * @param index
  608. * @returns {GraphicalStaffEntry}
  609. */
  610. public getStaffEntry(index: number): GraphicalStaffEntry {
  611. const container: VerticalGraphicalStaffEntryContainer = this.VerticalGraphicalStaffEntryContainers[index];
  612. let staffEntry: GraphicalStaffEntry = undefined;
  613. try {
  614. for (let idx: number = 0, len: number = container.StaffEntries.length; idx < len; ++idx) {
  615. const entry: GraphicalStaffEntry = container.StaffEntries[idx];
  616. if (!entry || !entry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  617. continue;
  618. }
  619. if (!staffEntry) {
  620. staffEntry = entry;
  621. } else if (entry.PositionAndShape && staffEntry.PositionAndShape) {
  622. if (staffEntry.PositionAndShape.RelativePosition.x > entry.PositionAndShape.RelativePosition.x) {
  623. staffEntry = entry;
  624. }
  625. }
  626. }
  627. } catch (ex) {
  628. log.info("GraphicalMusicSheet.getStaffEntry", ex);
  629. }
  630. return staffEntry;
  631. }
  632. /**
  633. * Returns the index of the closest previous (earlier) vertical container which has at least some visible staff entry, with respect to the given index.
  634. * @param index
  635. * @returns {number}
  636. * @constructor
  637. */
  638. public GetPreviousVisibleContainerIndex(index: number): number {
  639. for (let i: number = index - 1; i >= 0; i--) {
  640. const entries: GraphicalStaffEntry[] = this.verticalGraphicalStaffEntryContainers[i].StaffEntries;
  641. for (let idx: number = 0, len: number = entries.length; idx < len; ++idx) {
  642. const entry: GraphicalStaffEntry = entries[idx];
  643. if (entry && entry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  644. return i;
  645. }
  646. }
  647. }
  648. return -1;
  649. }
  650. /**
  651. * Returns the index of the closest next (later) vertical container which has at least some visible staff entry, with respect to the given index.
  652. * @param index
  653. * @returns {number}
  654. * @constructor
  655. */
  656. public GetNextVisibleContainerIndex(index: number): number {
  657. for (let i: number = index + 1; i < this.verticalGraphicalStaffEntryContainers.length; ++i) {
  658. const entries: GraphicalStaffEntry[] = this.verticalGraphicalStaffEntryContainers[i].StaffEntries;
  659. for (let idx: number = 0, len: number = entries.length; idx < len; ++idx) {
  660. const entry: GraphicalStaffEntry = entries[idx];
  661. if (entry && entry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  662. return i;
  663. }
  664. }
  665. }
  666. return -1;
  667. }
  668. public findClosestLeftStaffEntry(fractionalIndex: number, searchOnlyVisibleEntries: boolean): GraphicalStaffEntry {
  669. let foundEntry: GraphicalStaffEntry = undefined;
  670. let leftIndex: number = Math.floor(fractionalIndex);
  671. leftIndex = Math.min(this.VerticalGraphicalStaffEntryContainers.length - 1, leftIndex);
  672. for (let i: number = leftIndex; i >= 0; i--) {
  673. foundEntry = this.getStaffEntry(i);
  674. if (foundEntry) {
  675. if (searchOnlyVisibleEntries) {
  676. if (foundEntry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  677. return foundEntry;
  678. }
  679. } else {
  680. return foundEntry;
  681. }
  682. }
  683. }
  684. return undefined;
  685. }
  686. public findClosestRightStaffEntry(fractionalIndex: number, returnOnlyVisibleEntries: boolean): GraphicalStaffEntry {
  687. let foundEntry: GraphicalStaffEntry = undefined;
  688. const rightIndex: number = Math.max(0, Math.ceil(fractionalIndex));
  689. for (let i: number = rightIndex; i < this.VerticalGraphicalStaffEntryContainers.length; i++) {
  690. foundEntry = this.getStaffEntry(i);
  691. if (foundEntry) {
  692. if (returnOnlyVisibleEntries) {
  693. if (foundEntry.sourceStaffEntry.ParentStaff.ParentInstrument.Visible) {
  694. return foundEntry;
  695. }
  696. } else {
  697. return foundEntry;
  698. }
  699. }
  700. }
  701. return undefined;
  702. }
  703. public calculateCursorLineAtTimestamp(musicTimestamp: Fraction, styleEnum: OutlineAndFillStyleEnum): GraphicalLine {
  704. const result: [number, MusicSystem] = this.calculateXPositionFromTimestamp(musicTimestamp);
  705. const xPos: number = result[0];
  706. const correspondingMusicSystem: MusicSystem = result[1];
  707. if (!correspondingMusicSystem || correspondingMusicSystem.StaffLines.length === 0) {
  708. return undefined;
  709. }
  710. const yCoordinate: number = correspondingMusicSystem.PositionAndShape.AbsolutePosition.y;
  711. const height: number = CollectionUtil.last(correspondingMusicSystem.StaffLines).PositionAndShape.RelativePosition.y + 4;
  712. return new GraphicalLine(new PointF2D(xPos, yCoordinate), new PointF2D(xPos, yCoordinate + height), 3, styleEnum);
  713. }
  714. public calculateXPositionFromTimestamp(timeStamp: Fraction): [number, MusicSystem] {
  715. let currentMusicSystem: MusicSystem = undefined;
  716. const fractionalIndex: number = this.GetInterpolatedIndexInVerticalContainers(timeStamp);
  717. const previousStaffEntry: GraphicalStaffEntry = this.findClosestLeftStaffEntry(fractionalIndex, true);
  718. const nextStaffEntry: GraphicalStaffEntry = this.findClosestRightStaffEntry(fractionalIndex, true);
  719. const currentTimeStamp: number = timeStamp.RealValue;
  720. if (!previousStaffEntry && !nextStaffEntry) {
  721. return [0, undefined];
  722. }
  723. let previousStaffEntryMusicSystem: MusicSystem = undefined;
  724. if (previousStaffEntry) {
  725. // TODO sometimes one of these ParentStaffLine is undefined, either fix this or handle it here
  726. previousStaffEntryMusicSystem = previousStaffEntry.parentMeasure.ParentStaffLine?.ParentMusicSystem;
  727. } else {
  728. previousStaffEntryMusicSystem = nextStaffEntry.parentMeasure.ParentStaffLine?.ParentMusicSystem;
  729. }
  730. let nextStaffEntryMusicSystem: MusicSystem = undefined;
  731. if (nextStaffEntry) {
  732. nextStaffEntryMusicSystem = nextStaffEntry.parentMeasure.ParentStaffLine?.ParentMusicSystem;
  733. } else {
  734. nextStaffEntryMusicSystem = previousStaffEntry.parentMeasure.ParentStaffLine?.ParentMusicSystem;
  735. }
  736. if (previousStaffEntryMusicSystem === nextStaffEntryMusicSystem) {
  737. currentMusicSystem = previousStaffEntryMusicSystem;
  738. let fraction: number;
  739. let previousStaffEntryPositionX: number;
  740. let nextStaffEntryPositionX: number;
  741. if (!previousStaffEntry) {
  742. previousStaffEntryPositionX = nextStaffEntryPositionX = nextStaffEntry.PositionAndShape.AbsolutePosition.x;
  743. fraction = 0;
  744. } else if (!nextStaffEntry) {
  745. previousStaffEntryPositionX = previousStaffEntry.PositionAndShape.AbsolutePosition.x;
  746. nextStaffEntryPositionX = currentMusicSystem.GetRightBorderAbsoluteXPosition();
  747. const sm: SourceMeasure = previousStaffEntry.parentMeasure.parentSourceMeasure;
  748. fraction = (currentTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue) / (
  749. Fraction.plus(sm.AbsoluteTimestamp, sm.Duration).RealValue - previousStaffEntry.getAbsoluteTimestamp().RealValue);
  750. } else {
  751. previousStaffEntryPositionX = previousStaffEntry.PositionAndShape.AbsolutePosition.x;
  752. nextStaffEntryPositionX = nextStaffEntry.PositionAndShape.AbsolutePosition.x;
  753. if (previousStaffEntry === nextStaffEntry) {
  754. fraction = 0;
  755. } else {
  756. fraction = (currentTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue) /
  757. (nextStaffEntry.getAbsoluteTimestamp().RealValue - previousStaffEntry.getAbsoluteTimestamp().RealValue);
  758. }
  759. }
  760. fraction = Math.min(1, Math.max(0, fraction));
  761. const interpolatedXPosition: number = previousStaffEntryPositionX + fraction * (nextStaffEntryPositionX - previousStaffEntryPositionX);
  762. return [interpolatedXPosition, currentMusicSystem];
  763. } else {
  764. const nextSystemLeftBorderTimeStamp: number = nextStaffEntry.parentMeasure.parentSourceMeasure.AbsoluteTimestamp.RealValue;
  765. let fraction: number;
  766. let interpolatedXPosition: number;
  767. if (currentTimeStamp < nextSystemLeftBorderTimeStamp) {
  768. currentMusicSystem = previousStaffEntryMusicSystem;
  769. const previousStaffEntryPositionX: number = previousStaffEntry.PositionAndShape.AbsolutePosition.x;
  770. const previousSystemRightBorderX: number = currentMusicSystem.GetRightBorderAbsoluteXPosition();
  771. fraction = (currentTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue) /
  772. (nextSystemLeftBorderTimeStamp - previousStaffEntry.getAbsoluteTimestamp().RealValue);
  773. fraction = Math.min(1, Math.max(0, fraction));
  774. interpolatedXPosition = previousStaffEntryPositionX + fraction * (previousSystemRightBorderX - previousStaffEntryPositionX);
  775. } else {
  776. currentMusicSystem = nextStaffEntryMusicSystem;
  777. const nextStaffEntryPositionX: number = nextStaffEntry.PositionAndShape.AbsolutePosition.x;
  778. const nextSystemLeftBorderX: number = currentMusicSystem.GetLeftBorderAbsoluteXPosition();
  779. fraction = (currentTimeStamp - nextSystemLeftBorderTimeStamp) /
  780. (nextStaffEntry.getAbsoluteTimestamp().RealValue - nextSystemLeftBorderTimeStamp);
  781. fraction = Math.min(1, Math.max(0, fraction));
  782. interpolatedXPosition = nextSystemLeftBorderX + fraction * (nextStaffEntryPositionX - nextSystemLeftBorderX);
  783. }
  784. return [interpolatedXPosition, currentMusicSystem];
  785. }
  786. }
  787. public GetNumberOfVisibleInstruments(): number {
  788. let visibleInstrumentCount: number = 0;
  789. for (let idx: number = 0, len: number = this.musicSheet.Instruments.length; idx < len; ++idx) {
  790. const instrument: Instrument = this.musicSheet.Instruments[idx];
  791. if (instrument.Visible === true) {
  792. visibleInstrumentCount++;
  793. }
  794. }
  795. return visibleInstrumentCount;
  796. }
  797. public GetNumberOfFollowedInstruments(): number {
  798. let followedInstrumentCount: number = 0;
  799. for (let idx: number = 0, len: number = this.musicSheet.Instruments.length; idx < len; ++idx) {
  800. const instrument: Instrument = this.musicSheet.Instruments[idx];
  801. if (instrument.Following === true) {
  802. followedInstrumentCount++;
  803. }
  804. }
  805. return followedInstrumentCount;
  806. }
  807. /*public GetGraphicalFromSourceMeasure(sourceMeasure: SourceMeasure): GraphicalMeasure[] {
  808. return this.sourceToGraphicalMeasureLinks.getValue(sourceMeasure); // TODO gets wrong measure because sourceMeasure is not a valid key
  809. }*/
  810. public GetGraphicalFromSourceStaffEntry(sourceStaffEntry: SourceStaffEntry): GraphicalStaffEntry {
  811. const graphicalMeasure: GraphicalMeasure = sourceStaffEntry.VerticalContainerParent.ParentMeasure.VerticalMeasureList
  812. [sourceStaffEntry.ParentStaff.idInMusicSheet];
  813. return graphicalMeasure.findGraphicalStaffEntryFromTimestamp(sourceStaffEntry.Timestamp);
  814. }
  815. private CalculateDistance(pt1: PointF2D, pt2: PointF2D): number {
  816. const deltaX: number = pt1.x - pt2.x;
  817. const deltaY: number = pt1.y - pt2.y;
  818. return (deltaX * deltaX) + (deltaY * deltaY);
  819. }
  820. /**
  821. * Return the longest StaffEntry duration from a GraphicalVerticalContainer.
  822. * @param index the index of the vertical container
  823. * @returns {Fraction}
  824. */
  825. private getLongestStaffEntryDuration(index: number): Fraction {
  826. let maxLength: Fraction = new Fraction(0, 1);
  827. for (const graphicalStaffEntry of this.verticalGraphicalStaffEntryContainers[index].StaffEntries) {
  828. if (!graphicalStaffEntry) {
  829. continue;
  830. }
  831. const maxLengthInStaffEntry: Fraction = graphicalStaffEntry.findStaffEntryMaxNoteLength();
  832. if (maxLength.lt(maxLengthInStaffEntry)) {
  833. maxLength = maxLengthInStaffEntry;
  834. }
  835. }
  836. return maxLength;
  837. }
  838. }
  839. export class SystemImageProperties {
  840. public positionInPixels: PointF2D;
  841. public systemImageId: number;
  842. public system: MusicSystem;
  843. }