GraphicalMusicSheet.ts 42 KB

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