GraphicalMusicSheet.ts 41 KB

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