12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235 |
- import {GraphicalMeasure} from "./GraphicalMeasure";
- import {GraphicalMusicPage} from "./GraphicalMusicPage";
- import {EngravingRules} from "./EngravingRules";
- import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
- import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
- import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
- import {SourceMeasure} from "../VoiceData/SourceMeasure";
- import {MusicSystem} from "./MusicSystem";
- import {BoundingBox} from "./BoundingBox";
- import {Staff} from "../VoiceData/Staff";
- import {Instrument} from "../Instrument";
- import {PointF2D} from "../../Common/DataObjects/PointF2D";
- import {StaffLine} from "./StaffLine";
- import {GraphicalLine} from "./GraphicalLine";
- import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
- import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
- import {SystemLinesEnum} from "./SystemLinesEnum";
- import {GraphicalMusicSheet} from "./GraphicalMusicSheet";
- import {MusicSheetCalculator} from "./MusicSheetCalculator";
- import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
- import {CollectionUtil} from "../../Util/CollectionUtil";
- import {SystemLinePosition} from "./SystemLinePosition";
- export class MusicSystemBuilder {
- protected measureList: GraphicalMeasure[][];
- protected graphicalMusicSheet: GraphicalMusicSheet;
- protected currentSystemParams: SystemBuildParameters;
- protected numberOfVisibleStaffLines: number;
- protected rules: EngravingRules;
- protected measureListIndex: number;
- protected musicSystems: MusicSystem[] = [];
- /**
- * Does the mapping from the currently visible staves to the global staff-list of the music sheet.
- */
- protected visibleStaffIndices: number[];
- protected activeRhythm: RhythmInstruction[];
- protected activeKeys: KeyInstruction[];
- protected activeClefs: ClefInstruction[];
- protected globalSystemIndex: number = 0;
- protected leadSheet: boolean = false;
- public initialize(
- graphicalMusicSheet: GraphicalMusicSheet, measureList: GraphicalMeasure[][], numberOfStaffLines: number): void {
- this.leadSheet = graphicalMusicSheet.LeadSheet;
- this.graphicalMusicSheet = graphicalMusicSheet;
- this.rules = this.graphicalMusicSheet.ParentMusicSheet.Rules;
- this.measureList = measureList;
- this.numberOfVisibleStaffLines = numberOfStaffLines;
- this.activeRhythm = new Array(this.numberOfVisibleStaffLines);
- this.activeKeys = new Array(this.numberOfVisibleStaffLines);
- this.activeClefs = new Array(this.numberOfVisibleStaffLines);
- this.initializeActiveInstructions(this.measureList[0]);
- }
- public buildMusicSystems(): MusicSystem[] {
- const systemMaxWidth: number = this.getFullPageSystemWidth();
- let prevMeasureEndsPart: boolean = false;
- this.measureListIndex = 0;
- this.currentSystemParams = new SystemBuildParameters();
- // the first System - create also its Labels
- this.currentSystemParams.currentSystem = this.initMusicSystem();
- // let numberOfMeasures: number = 0;
- // for (let idx: number = 0, len: number = this.measureList.length; idx < len; ++idx) {
- // if (this.measureList[idx].length > 0) {
- // numberOfMeasures++;
- // }
- // }
- // console.log(`numberOfMeasures: ${numberOfMeasures}`);
- // go through measures and add to system until system gets too long -> finish system and start next system [line break, new system].
- while (this.measureListIndex < this.measureList.length) {
- const graphicalMeasures: GraphicalMeasure[] = this.measureList[this.measureListIndex];
- if (!graphicalMeasures || !graphicalMeasures[0]) {
- this.measureListIndex++;
- continue; // previous measure was probably multi-rest, skip this one
- }
- for (let idx: number = 0, len: number = graphicalMeasures.length; idx < len; ++idx) {
- graphicalMeasures[idx].resetLayout();
- }
- const sourceMeasure: SourceMeasure = graphicalMeasures[0].parentSourceMeasure;
- const sourceMeasureEndsPart: boolean = sourceMeasure.HasEndLine;
- const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
- const isFirstSourceMeasure: boolean = sourceMeasure === this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
- let currentMeasureBeginInstructionsWidth: number = this.rules.MeasureLeftMargin;
- let currentMeasureEndInstructionsWidth: number = 0;
- // calculate the current Measure Width:
- // The width of a measure is build up from
- // 1. the begin instructions (clef, Key, Rhythm),
- // 2. the staff entries (= notes) and
- // 3. the end instructions (actually only clefs)
- const measureStartLine: SystemLinesEnum = this.getMeasureStartLine();
- currentMeasureBeginInstructionsWidth += this.getLineWidth(graphicalMeasures[0], measureStartLine, isSystemStartMeasure);
- if (!this.leadSheet) {
- let forceShowRhythm: boolean = false;
- if (prevMeasureEndsPart && this.rules.ShowRhythmAgainAfterPartEndOrFinalBarline) {
- forceShowRhythm = true;
- }
- currentMeasureBeginInstructionsWidth += this.addBeginInstructions( graphicalMeasures,
- isSystemStartMeasure,
- isFirstSourceMeasure || forceShowRhythm);
- // forceShowRhythm could be a fourth parameter instead in addBeginInstructions, but only affects RhythmInstruction for now.
- currentMeasureEndInstructionsWidth += this.addEndInstructions(graphicalMeasures);
- }
- let currentMeasureVarWidth: number = 0;
- for (let i: number = 0; i < this.numberOfVisibleStaffLines; i++) {
- currentMeasureVarWidth = Math.max(currentMeasureVarWidth, graphicalMeasures[i].minimumStaffEntriesWidth);
- }
- // take into account the LineWidth after each Measure
- const measureEndLine: SystemLinesEnum = this.getMeasureEndLine();
- currentMeasureEndInstructionsWidth += this.getLineWidth(graphicalMeasures[0], measureEndLine, isSystemStartMeasure);
- let nextMeasureBeginInstructionWidth: number = this.rules.MeasureLeftMargin;
- // Check if there are key or rhythm change instructions within the next measure:
- let nextSourceMeasure: SourceMeasure = undefined;
- if (this.measureListIndex + 1 < this.measureList.length) {
- const nextGraphicalMeasures: GraphicalMeasure[] = this.measureList[this.measureListIndex + 1];
- // TODO: consider multirest. then the next graphical measure may not exist. but there shouldn't be hidden changes here.
- nextSourceMeasure = nextGraphicalMeasures[0]?.parentSourceMeasure;
- if (nextSourceMeasure?.hasBeginInstructions()) {
- nextMeasureBeginInstructionWidth += this.addBeginInstructions(nextGraphicalMeasures, false, false);
- }
- }
- let totalMeasureWidth: number = currentMeasureBeginInstructionsWidth + currentMeasureEndInstructionsWidth + currentMeasureVarWidth;
- if (graphicalMeasures[0]?.parentSourceMeasure?.multipleRestMeasures) {
- totalMeasureWidth = this.rules.MultipleRestMeasureDefaultWidth; // default 4 (12 seems too large)
- }
- const measureFitsInSystem: boolean = this.currentSystemParams.currentWidth + totalMeasureWidth + nextMeasureBeginInstructionWidth < systemMaxWidth;
- const doXmlPageBreak: boolean = this.rules.NewPageAtXMLNewPageAttribute && sourceMeasure.printNewPageXml;
- const doXmlLineBreak: boolean = doXmlPageBreak || // also create new system if doing page break
- (this.rules.NewSystemAtXMLNewSystemAttribute && sourceMeasure.printNewSystemXml);
- if (isSystemStartMeasure || (measureFitsInSystem && !doXmlLineBreak)) {
- this.addMeasureToSystem(
- graphicalMeasures, measureStartLine, measureEndLine, totalMeasureWidth,
- currentMeasureBeginInstructionsWidth, currentMeasureVarWidth, currentMeasureEndInstructionsWidth
- );
- this.updateActiveClefs(sourceMeasure, graphicalMeasures);
- this.measureListIndex++;
- if (sourceMeasureEndsPart) {
- this.finalizeCurrentAndCreateNewSystem(graphicalMeasures, !this.rules.StretchLastSystemLine, false);
- }
- prevMeasureEndsPart = sourceMeasureEndsPart;
- } else {
- // finalize current system and prepare a new one
- this.finalizeCurrentAndCreateNewSystem(graphicalMeasures, false, true, doXmlPageBreak);
- // don't increase measure index to check this measure now again
- // don't set prevMeasureEndsPart in this case! because we will loop with the same measure again.
- }
- }
- if (this.currentSystemParams.systemMeasures.length > 0) {
- this.finalizeCurrentAndCreateNewSystem(this.measureList[this.measureList.length - 1], !this.rules.StretchLastSystemLine, false);
- }
- return this.musicSystems;
- }
- /**
- * calculates the y positions of the staff lines within a system and
- * furthermore the y positions of the systems themselves.
- */
- public calculateSystemYLayout(): void {
- for (const musicSystem of this.musicSystems) {
- this.optimizeDistanceBetweenStaffLines(musicSystem);
- }
- // set y positions of systems using the previous system and a fixed distance.
- this.calculateMusicSystemsRelativePositions();
- }
- /**
- * Set the Width of the staff-Measures of one source measure.
- * @param graphicalMeasures
- * @param width
- * @param beginInstrWidth
- * @param endInstrWidth
- */
- protected setMeasureWidth(graphicalMeasures: GraphicalMeasure[], width: number, beginInstrWidth: number, endInstrWidth: number): void {
- for (let idx: number = 0, len: number = graphicalMeasures.length; idx < len; ++idx) {
- const measure: GraphicalMeasure = graphicalMeasures[idx];
- measure.setWidth(width);
- if (beginInstrWidth > 0) {
- measure.beginInstructionsWidth = beginInstrWidth;
- }
- if (endInstrWidth > 0) {
- measure.endInstructionsWidth = endInstrWidth;
- }
- }
- }
- /**
- * When the actual source measure doesn't fit any more, this method finalizes the current system and
- * opens up a new empty system, where the actual measure will be added in the next iteration.
- * @param measures
- * @param isPartEndingSystem
- */
- protected finalizeCurrentAndCreateNewSystem(measures: GraphicalMeasure[],
- systemEndsPart: boolean = false,
- checkExtraInstructionMeasure: boolean = true,
- startNewPage: boolean = false): void {
- this.currentSystemParams.currentSystem.breaksPage = startNewPage;
- this.adaptRepetitionLineWithIfNeeded();
- if (measures !== undefined &&
- checkExtraInstructionMeasure) {
- this.checkAndCreateExtraInstructionMeasure(measures);
- }
- this.stretchMusicSystem(systemEndsPart);
- this.currentSystemParams = new SystemBuildParameters();
- if (measures !== undefined &&
- this.measureListIndex < this.measureList.length) {
- this.currentSystemParams.currentSystem = this.initMusicSystem();
- }
- }
- /**
- * If a line repetition is ending and a new line repetition is starting at the end of the system,
- * the double repetition line has to be split into two: one at the currently ending system and
- * one at the next system.
- * (this should be refactored at some point to not use a combined end/start line but always separated lines)
- */
- protected adaptRepetitionLineWithIfNeeded(): void {
- const systemMeasures: MeasureBuildParameters[] = this.currentSystemParams.systemMeasures;
- if (systemMeasures.length >= 1) {
- const measures: GraphicalMeasure[] =
- this.currentSystemParams.currentSystem.GraphicalMeasures[this.currentSystemParams.currentSystem.GraphicalMeasures.length - 1];
- const measureParams: MeasureBuildParameters = systemMeasures[systemMeasures.length - 1];
- let diff: number = 0.0;
- if (measureParams.endLine === SystemLinesEnum.DotsBoldBoldDots) {
- measureParams.endLine = SystemLinesEnum.DotsThinBold;
- diff = measures[0].getLineWidth(SystemLinesEnum.DotsBoldBoldDots) / 2 - measures[0].getLineWidth(SystemLinesEnum.DotsThinBold);
- }
- this.currentSystemParams.currentSystemFixWidth -= diff;
- for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
- const measure: GraphicalMeasure = measures[idx];
- measure.endInstructionsWidth -= diff;
- }
- }
- }
- protected addMeasureToSystem(
- graphicalMeasures: GraphicalMeasure[], measureStartLine: SystemLinesEnum, measureEndLine: SystemLinesEnum,
- totalMeasureWidth: number, currentMeasureBeginInstructionsWidth: number, currentVarWidth: number, currentMeasureEndInstructionsWidth: number
- ): void {
- this.currentSystemParams.systemMeasures.push({beginLine: measureStartLine, endLine: measureEndLine});
- this.setMeasureWidth(
- graphicalMeasures, totalMeasureWidth, currentMeasureBeginInstructionsWidth, currentMeasureEndInstructionsWidth
- );
- this.addStaveMeasuresToSystem(graphicalMeasures);
- this.currentSystemParams.currentWidth += totalMeasureWidth;
- this.currentSystemParams.currentSystemFixWidth += currentMeasureBeginInstructionsWidth + currentMeasureEndInstructionsWidth;
- this.currentSystemParams.currentSystemVarWidth += currentVarWidth;
- this.currentSystemParams.systemMeasureIndex++;
- }
- /**
- * Initialize a new [[MusicSystem]].
- * @returns {MusicSystem}
- */
- protected initMusicSystem(): MusicSystem {
- const musicSystem: MusicSystem = MusicSheetCalculator.symbolFactory.createMusicSystem(this.globalSystemIndex++, this.rules);
- this.musicSystems.push(musicSystem);
- this.layoutSystemStaves(musicSystem);
- musicSystem.createMusicSystemLabel(
- this.rules.InstrumentLabelTextHeight,
- this.rules.SystemLabelsRightMargin,
- this.rules.LabelMarginBorderFactor,
- this.musicSystems.length === 1
- );
- return musicSystem;
- }
- /**
- * Get the width the system should have for a given page width.
- * @returns {number}
- */
- protected getFullPageSystemWidth(): number {
- return this.graphicalMusicSheet.ParentMusicSheet.pageWidth - this.rules.PageLeftMargin
- - this.rules.PageRightMargin - this.rules.SystemLeftMargin - this.rules.SystemRightMargin;
- }
- protected layoutSystemStaves(musicSystem: MusicSystem): void {
- const systemWidth: number = this.getFullPageSystemWidth();
- const boundingBox: BoundingBox = musicSystem.PositionAndShape;
- boundingBox.BorderLeft = 0.0;
- boundingBox.BorderRight = systemWidth;
- boundingBox.BorderTop = 0.0;
- const staffList: Staff[] = [];
- const instruments: Instrument[] = this.graphicalMusicSheet.ParentMusicSheet.Instruments;
- for (let idx: number = 0, len: number = instruments.length; idx < len; ++idx) {
- const instrument: Instrument = instruments[idx];
- if (!instrument.Visible || instrument.Voices.length === 0) {
- continue;
- }
- for (let idx2: number = 0, len2: number = instrument.Staves.length; idx2 < len2; ++idx2) {
- const staff: Staff = instrument.Staves[idx2];
- staffList.push(staff);
- }
- }
- let multiLyrics: boolean = false;
- if (this.leadSheet) {
- for (let idx: number = 0, len: number = staffList.length; idx < len; ++idx) {
- const staff: Staff = staffList[idx];
- if (staff.ParentInstrument.LyricVersesNumbers.length > 1) {
- multiLyrics = true;
- break;
- }
- }
- }
- let yOffsetSum: number = 0;
- for (let i: number = 0; i < staffList.length; i++) {
- this.addStaffLineToMusicSystem(musicSystem, yOffsetSum, staffList[i]);
- yOffsetSum += this.rules.StaffHeight;
- if (i + 1 < staffList.length) {
- let yOffset: number = 0;
- if (this.leadSheet && !multiLyrics) {
- yOffset = 2.5;
- } else {
- if (staffList[i].ParentInstrument === staffList[i + 1].ParentInstrument) {
- yOffset = this.rules.BetweenStaffDistance;
- } else {
- yOffset = this.rules.StaffDistance;
- }
- }
- yOffsetSum += yOffset;
- }
- }
- boundingBox.BorderBottom = yOffsetSum;
- }
- /**
- * Calculate the [[StaffLine]](s) needed for a [[MusicSystem]].
- * @param musicSystem
- * @param relativeYPosition
- * @param staff
- */
- protected addStaffLineToMusicSystem(musicSystem: MusicSystem, relativeYPosition: number, staff: Staff): void {
- if (musicSystem) {
- const staffLine: StaffLine = MusicSheetCalculator.symbolFactory.createStaffLine(musicSystem, staff);
- musicSystem.StaffLines.push(staffLine);
- const boundingBox: BoundingBox = staffLine.PositionAndShape;
- const relativePosition: PointF2D = new PointF2D();
- relativePosition.x = 0.0;
- boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width;
- relativePosition.y = relativeYPosition;
- boundingBox.RelativePosition = relativePosition;
- boundingBox.BorderLeft = 0.0;
- boundingBox.BorderTop = 0.0;
- boundingBox.BorderBottom = this.rules.StaffHeight;
- for (let i: number = 0; i < 5; i++) {
- const start: PointF2D = new PointF2D();
- start.x = 0.0;
- start.y = i * this.rules.StaffHeight / 4;
- const end: PointF2D = new PointF2D();
- end.x = staffLine.PositionAndShape.Size.width;
- end.y = i * this.rules.StaffHeight / 4;
- if (this.leadSheet) {
- start.y = end.y = 0;
- }
- staffLine.StaffLines[i] = new GraphicalLine(start, end, this.rules.StaffLineWidth);
- }
- }
- }
- /**
- * Initialize the active Instructions from the first [[SourceMeasure]] of first [[SourceMusicPart]].
- * @param measureList
- */
- protected initializeActiveInstructions(measureList: GraphicalMeasure[]): void {
- const firstSourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
- if (firstSourceMeasure) {
- this.visibleStaffIndices = this.graphicalMusicSheet.getVisibleStavesIndicesFromSourceMeasure(measureList);
- for (let i: number = 0, len: number = this.visibleStaffIndices.length; i < len; i++) {
- const staffIndex: number = this.visibleStaffIndices[i];
- const graphicalMeasure: GraphicalMeasure = this.graphicalMusicSheet
- .getGraphicalMeasureFromSourceMeasureAndIndex(firstSourceMeasure, staffIndex);
- this.activeClefs[i] = <ClefInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[0];
- let keyInstruction: KeyInstruction = KeyInstruction.copy(
- <KeyInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[1]);
- keyInstruction = this.transposeKeyInstruction(keyInstruction, graphicalMeasure);
- this.activeKeys[i] = keyInstruction;
- this.activeRhythm[i] = <RhythmInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[2];
- }
- }
- }
- protected transposeKeyInstruction(keyInstruction: KeyInstruction, graphicalMeasure: GraphicalMeasure): KeyInstruction {
- if (this.graphicalMusicSheet.ParentMusicSheet.Transpose !== 0
- && graphicalMeasure.ParentStaff.ParentInstrument.MidiInstrumentId !== MidiInstrument.Percussion
- && MusicSheetCalculator.transposeCalculator !== undefined
- ) {
- MusicSheetCalculator.transposeCalculator.transposeKey(
- keyInstruction,
- this.graphicalMusicSheet.ParentMusicSheet.Transpose
- );
- }
- return keyInstruction;
- }
- /**
- * Calculate the width needed for Instructions (Key, Clef, Rhythm, Repetition) for the measure.
- * @param measures
- * @param isSystemFirstMeasure
- * @param isFirstSourceMeasure
- * @returns {number}
- */
- protected addBeginInstructions(measures: GraphicalMeasure[], isSystemFirstMeasure: boolean, isFirstSourceMeasure: boolean): number {
- const measureCount: number = measures.length;
- if (measureCount === 0) {
- return 0;
- }
- let totalBeginInstructionLengthX: number = 0.0;
- const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
- for (let idx: number = 0; idx < measureCount; ++idx) {
- const measure: GraphicalMeasure = measures[idx];
- const staffIndex: number = this.visibleStaffIndices[idx];
- const beginInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.FirstInstructionsStaffEntries[staffIndex];
- const beginInstructionLengthX: number = this.AddInstructionsAtMeasureBegin(
- beginInstructionsStaffEntry, measure,
- idx, isFirstSourceMeasure,
- isSystemFirstMeasure
- );
- totalBeginInstructionLengthX = Math.max(totalBeginInstructionLengthX, beginInstructionLengthX);
- }
- return totalBeginInstructionLengthX;
- }
- /**
- * Calculates the width needed for Instructions (Clef, Repetition) for the measure.
- * @param measures
- * @returns {number}
- */
- protected addEndInstructions(measures: GraphicalMeasure[]): number {
- const measureCount: number = measures.length;
- if (measureCount === 0) {
- return 0;
- }
- let totalEndInstructionLengthX: number = 0.5;
- const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
- for (let idx: number = 0; idx < measureCount; idx++) {
- const measure: GraphicalMeasure = measures[idx];
- const staffIndex: number = this.visibleStaffIndices[idx];
- const endInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.LastInstructionsStaffEntries[staffIndex];
- const endInstructionLengthX: number = this.addInstructionsAtMeasureEnd(endInstructionsStaffEntry, measure);
- totalEndInstructionLengthX = Math.max(totalEndInstructionLengthX, endInstructionLengthX);
- }
- return totalEndInstructionLengthX;
- }
- protected AddInstructionsAtMeasureBegin(firstEntry: SourceStaffEntry, measure: GraphicalMeasure,
- visibleStaffIdx: number, isFirstSourceMeasure: boolean, isSystemStartMeasure: boolean): number {
- let instructionsLengthX: number = 0;
- let currentClef: ClefInstruction = undefined;
- let currentKey: KeyInstruction = undefined;
- let currentRhythm: RhythmInstruction = undefined;
- if (firstEntry) {
- for (let idx: number = 0, len: number = firstEntry.Instructions.length; idx < len; ++idx) {
- const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
- if (abstractNotationInstruction instanceof ClefInstruction) {
- currentClef = <ClefInstruction>abstractNotationInstruction;
- } else if (abstractNotationInstruction instanceof KeyInstruction) {
- currentKey = <KeyInstruction>abstractNotationInstruction;
- } else if (abstractNotationInstruction instanceof RhythmInstruction) {
- currentRhythm = <RhythmInstruction>abstractNotationInstruction;
- }
- }
- }
- if (isSystemStartMeasure) {
- if (!currentClef) {
- currentClef = this.activeClefs[visibleStaffIdx];
- }
- if (!currentKey) {
- currentKey = this.activeKeys[visibleStaffIdx];
- }
- if (isFirstSourceMeasure && !currentRhythm) {
- currentRhythm = this.activeRhythm[visibleStaffIdx];
- }
- }
- let clefAdded: boolean = false;
- let keyAdded: boolean = false;
- let rhythmAdded: boolean = false;
- if (currentClef) {
- measure.addClefAtBegin(currentClef);
- clefAdded = true;
- } else {
- currentClef = this.activeClefs[visibleStaffIdx];
- }
- if (currentKey) {
- currentKey = this.transposeKeyInstruction(currentKey, measure);
- const previousKey: KeyInstruction = isSystemStartMeasure ? undefined : this.activeKeys[visibleStaffIdx];
- measure.addKeyAtBegin(currentKey, previousKey, currentClef);
- keyAdded = true;
- }
- if (currentRhythm !== undefined && currentRhythm.PrintObject && this.rules.RenderTimeSignatures) {
- measure.addRhythmAtBegin(currentRhythm);
- rhythmAdded = true;
- }
- if (clefAdded || keyAdded || rhythmAdded) {
- instructionsLengthX += measure.beginInstructionsWidth;
- if (rhythmAdded) {
- instructionsLengthX += this.rules.RhythmRightMargin;
- }
- }
- return instructionsLengthX;
- }
- protected addInstructionsAtMeasureEnd(lastEntry: SourceStaffEntry, measure: GraphicalMeasure): number {
- if (!lastEntry || !lastEntry.Instructions || lastEntry.Instructions.length === 0) {
- return 0;
- }
- for (let idx: number = 0, len: number = lastEntry.Instructions.length; idx < len; ++idx) {
- const abstractNotationInstruction: AbstractNotationInstruction = lastEntry.Instructions[idx];
- if (abstractNotationInstruction instanceof ClefInstruction) {
- const activeClef: ClefInstruction = <ClefInstruction>abstractNotationInstruction;
- measure.addClefAtEnd(activeClef);
- }
- }
- return this.rules.MeasureRightMargin + measure.endInstructionsWidth;
- }
- /**
- * Track down and update the active ClefInstruction in Measure's StaffEntries.
- * This has to be done after the measure is added to a system
- * (otherwise already the check if the measure fits to the system would update the active clefs..)
- * @param measure
- * @param graphicalMeasures
- */
- protected updateActiveClefs(measure: SourceMeasure, graphicalMeasures: GraphicalMeasure[]): void {
- for (let visStaffIdx: number = 0, len: number = graphicalMeasures.length; visStaffIdx < len; visStaffIdx++) {
- const staffIndex: number = this.visibleStaffIndices[visStaffIdx];
- const firstEntry: SourceStaffEntry = measure.FirstInstructionsStaffEntries[staffIndex];
- if (firstEntry) {
- for (let idx: number = 0, len2: number = firstEntry.Instructions.length; idx < len2; ++idx) {
- const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
- if (abstractNotationInstruction instanceof ClefInstruction) {
- this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
- } else if (abstractNotationInstruction instanceof KeyInstruction) {
- this.activeKeys[visStaffIdx] = <KeyInstruction>abstractNotationInstruction;
- } else if (abstractNotationInstruction instanceof RhythmInstruction) {
- this.activeRhythm[visStaffIdx] = <RhythmInstruction>abstractNotationInstruction;
- }
- }
- }
- const entries: SourceStaffEntry[] = measure.getEntriesPerStaff(staffIndex);
- for (let idx: number = 0, len2: number = entries.length; idx < len2; ++idx) {
- const staffEntry: SourceStaffEntry = entries[idx];
- if (staffEntry.Instructions) {
- for (let idx2: number = 0, len3: number = staffEntry.Instructions.length; idx2 < len3; ++idx2) {
- const abstractNotationInstruction: AbstractNotationInstruction = staffEntry.Instructions[idx2];
- if (abstractNotationInstruction instanceof ClefInstruction) {
- this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
- }
- }
- }
- }
- const lastEntry: SourceStaffEntry = measure.LastInstructionsStaffEntries[staffIndex];
- if (lastEntry) {
- const instructions: AbstractNotationInstruction[] = lastEntry.Instructions;
- for (let idx: number = 0, len3: number = instructions.length; idx < len3; ++idx) {
- const abstractNotationInstruction: AbstractNotationInstruction = instructions[idx];
- if (abstractNotationInstruction instanceof ClefInstruction) {
- this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
- }
- }
- }
- }
- }
- /**
- * Check if an extra Instruction [[Measure]] is needed.
- * @param measures
- */
- protected checkAndCreateExtraInstructionMeasure(measures: GraphicalMeasure[]): void {
- const firstStaffEntries: SourceStaffEntry[] = measures[0].parentSourceMeasure.FirstInstructionsStaffEntries;
- const visibleInstructionEntries: SourceStaffEntry[] = [];
- for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
- const measure: GraphicalMeasure = measures[idx];
- visibleInstructionEntries.push(firstStaffEntries[measure.ParentStaff.idInMusicSheet]);
- }
- let maxMeasureWidth: number = 0;
- for (let visStaffIdx: number = 0, len: number = visibleInstructionEntries.length; visStaffIdx < len; ++visStaffIdx) {
- const sse: SourceStaffEntry = visibleInstructionEntries[visStaffIdx];
- if (!sse) {
- continue;
- }
- const instructions: AbstractNotationInstruction[] = sse.Instructions;
- let keyInstruction: KeyInstruction = undefined;
- let rhythmInstruction: RhythmInstruction = undefined;
- for (let idx2: number = 0, len2: number = instructions.length; idx2 < len2; ++idx2) {
- const instruction: AbstractNotationInstruction = instructions[idx2];
- if (instruction instanceof KeyInstruction && (<KeyInstruction>instruction).Key !== this.activeKeys[visStaffIdx].Key) {
- keyInstruction = <KeyInstruction>instruction;
- }
- if (instruction instanceof RhythmInstruction && (<RhythmInstruction>instruction) !== this.activeRhythm[visStaffIdx]) {
- rhythmInstruction = <RhythmInstruction>instruction;
- }
- }
- if (keyInstruction !== undefined || rhythmInstruction) {
- const measureWidth: number = this.addExtraInstructionMeasure(visStaffIdx, keyInstruction, rhythmInstruction);
- maxMeasureWidth = Math.max(maxMeasureWidth, measureWidth);
- }
- }
- if (maxMeasureWidth > 0) {
- this.currentSystemParams.systemMeasures.push({
- beginLine: SystemLinesEnum.None,
- endLine: SystemLinesEnum.None,
- });
- this.currentSystemParams.currentWidth += maxMeasureWidth;
- this.currentSystemParams.currentSystemFixWidth += maxMeasureWidth;
- }
- }
- protected addExtraInstructionMeasure(visStaffIdx: number, keyInstruction: KeyInstruction, rhythmInstruction: RhythmInstruction): number {
- const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
- const measures: GraphicalMeasure[] = [];
- const measure: GraphicalMeasure = MusicSheetCalculator.symbolFactory.createExtraGraphicalMeasure(currentSystem.StaffLines[visStaffIdx]);
- measures.push(measure);
- if (keyInstruction) {
- measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
- }
- if (rhythmInstruction !== undefined && rhythmInstruction.PrintObject) {
- measure.addRhythmAtBegin(rhythmInstruction);
- }
- measure.PositionAndShape.BorderLeft = 0.0;
- measure.PositionAndShape.BorderTop = 0.0;
- measure.PositionAndShape.BorderBottom = this.rules.StaffHeight;
- const width: number = this.rules.MeasureLeftMargin + measure.beginInstructionsWidth + this.rules.MeasureRightMargin;
- measure.PositionAndShape.BorderRight = width;
- currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
- return width;
- }
- /**
- * Add all current vertical Measures to currentSystem.
- * @param graphicalMeasures
- */
- protected addStaveMeasuresToSystem(graphicalMeasures: GraphicalMeasure[]): void {
- if (graphicalMeasures[0]) {
- const gmeasures: GraphicalMeasure[] = [];
- for (let i: number = 0; i < graphicalMeasures.length; i++) {
- gmeasures.push(graphicalMeasures[i]);
- }
- const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
- for (let visStaffIdx: number = 0; visStaffIdx < this.numberOfVisibleStaffLines; visStaffIdx++) {
- const measure: GraphicalMeasure = gmeasures[visStaffIdx];
- currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
- measure.ParentStaffLine = currentSystem.StaffLines[visStaffIdx];
- }
- currentSystem.AddGraphicalMeasures(gmeasures);
- }
- }
- /**
- * Return the width of the corresponding [[SystemLine]] and set the corresponding [[SystemLineEnum]].
- * @returns {SystemLinesEnum}
- */
- protected getMeasureStartLine(): SystemLinesEnum {
- const thisMeasureBeginsLineRep: boolean = this.thisMeasureBeginsLineRepetition();
- if (thisMeasureBeginsLineRep) {
- const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
- const isGlobalFirstMeasure: boolean = this.measureListIndex === 0;
- if (this.previousMeasureEndsLineRepetition() && !isSystemStartMeasure) {
- return SystemLinesEnum.DotsBoldBoldDots;
- }
- if (!isGlobalFirstMeasure) {
- return SystemLinesEnum.BoldThinDots;
- }
- }
- return SystemLinesEnum.None;
- }
- protected getMeasureEndLine(): SystemLinesEnum {
- let sourceMeasure: SourceMeasure = undefined;
- try {
- sourceMeasure = this.measureList[this.measureListIndex][0].parentSourceMeasure;
- if (this.rules.RenderMultipleRestMeasures && sourceMeasure.multipleRestMeasures > 1) {
- const newIndex: number = Math.min(
- this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length - 1, // safety check
- sourceMeasure.measureListIndex + sourceMeasure.multipleRestMeasures - 1
- // check the bar line of the last sourcemeasure in the multiple measure rest sequence
- );
- sourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[newIndex];
- // sourceMeasure = this.measureList[this.measureListIndex + sourceMeasure.multipleRestMeasures - 1][0].parentSourceMeasure;
- // this will be possible when the other GraphicalMeasures in the measureList aren't undefined anymore
- }
- } finally {
- // do nothing
- }
- if (this.nextMeasureBeginsLineRepetition() && this.thisMeasureEndsLineRepetition()) {
- return SystemLinesEnum.DotsBoldBoldDots;
- }
- if (this.thisMeasureEndsLineRepetition()) {
- return SystemLinesEnum.DotsThinBold;
- }
- // always end piece with final barline: not a good idea. user should be able to override final barline.
- // also, selecting range of measures to draw would always end with final barline, even if extract is from the middle of the piece
- // this was probably done before we parsed the barline type from XML.
- /*if (this.measureListIndex === this.measureList.length - 1 || this.measureList[this.measureListIndex][0].parentSourceMeasure.endsPiece) {
- return SystemLinesEnum.ThinBold;
- }*/
- if (this.nextMeasureHasKeyInstructionChange() || this.thisMeasureEndsWordRepetition() || this.nextMeasureBeginsWordRepetition()) {
- return SystemLinesEnum.DoubleThin;
- }
- if (!sourceMeasure) {
- return SystemLinesEnum.SingleThin;
- }
- if (sourceMeasure.endingBarStyleEnum !== undefined) {
- return sourceMeasure.endingBarStyleEnum;
- }
- // TODO: print an error message if the default fallback is used.
- return SystemLinesEnum.SingleThin;
- }
- /**
- * Return the width of the corresponding [[SystemLine]] and sets the corresponding [[SystemLineEnum]].
- * @param measure
- * @param systemLineEnum
- * @param isSystemStartMeasure
- * @returns {number}
- */
- protected getLineWidth(measure: GraphicalMeasure, systemLineEnum: SystemLinesEnum, isSystemStartMeasure: boolean): number {
- let width: number = measure.getLineWidth(systemLineEnum);
- if (systemLineEnum === SystemLinesEnum.DotsBoldBoldDots) {
- width /= 2;
- }
- if (isSystemStartMeasure && systemLineEnum === SystemLinesEnum.BoldThinDots) {
- width += this.rules.DistanceBetweenLastInstructionAndRepetitionBarline;
- }
- return width;
- }
- protected previousMeasureEndsLineRepetition(): boolean {
- if (this.measureListIndex === 0) {
- return false;
- }
- for (let idx: number = 0, len: number = this.measureList[this.measureListIndex - 1].length; idx < len; ++idx) {
- const measure: GraphicalMeasure = this.measureList[this.measureListIndex - 1][idx];
- if (measure.endsWithLineRepetition()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Check if at this [[Measure]] starts a [[Repetition]].
- * @returns {boolean}
- */
- protected thisMeasureBeginsLineRepetition(): boolean {
- for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
- const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
- if (measure.beginsWithLineRepetition()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Check if a [[Repetition]] starts at the next [[Measure]].
- * @returns {boolean}
- */
- protected nextMeasureBeginsLineRepetition(): boolean {
- const nextMeasureIndex: number = this.measureListIndex + 1;
- if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length
- || !this.measureList[nextMeasureIndex]) {
- return false;
- }
- for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {
- const measure: GraphicalMeasure = this.measureList[nextMeasureIndex][idx];
- if (measure.beginsWithLineRepetition()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Check if this [[Measure]] is a [[Repetition]] ending.
- * @returns {boolean}
- */
- protected thisMeasureEndsLineRepetition(): boolean {
- for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
- const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
- if (measure.endsWithLineRepetition()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Check if a [[Repetition]] starts at the next [[Measure]].
- * @returns {boolean}
- */
- protected nextMeasureBeginsWordRepetition(): boolean {
- const nextMeasureIndex: number = this.measureListIndex + 1;
- if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length ||
- nextMeasureIndex > this.measureList.length - 1) {
- return false;
- }
- for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {
- const measure: GraphicalMeasure = this.measureList[nextMeasureIndex][idx];
- if (measure.beginsWithWordRepetition()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Check if this [[Measure]] is a [[Repetition]] ending.
- * @returns {boolean}
- */
- protected thisMeasureEndsWordRepetition(): boolean {
- for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
- const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
- if (measure.endsWithWordRepetition()) {
- return true;
- }
- }
- return false;
- }
- /**
- * Check if the next [[Measure]] has a [[KeyInstruction]] change.
- * @returns {boolean}
- */
- protected nextMeasureHasKeyInstructionChange(): boolean {
- return this.getNextMeasureKeyInstruction() !== undefined;
- }
- protected getNextMeasureKeyInstruction(): KeyInstruction {
- if (this.measureListIndex < this.measureList.length - 1) {
- for (let visIndex: number = 0; visIndex < this.measureList[this.measureListIndex].length; visIndex++) {
- const sourceMeasure: SourceMeasure = this.measureList[this.measureListIndex + 1][visIndex]?.parentSourceMeasure;
- if (!sourceMeasure) {
- return undefined;
- }
- return sourceMeasure.getKeyInstruction(this.visibleStaffIndices[visIndex]);
- }
- }
- return undefined;
- }
- /**
- * Calculate the X ScalingFactor in order to strech the whole System.
- * @param systemFixWidth
- * @param systemVarWidth
- * @returns {number}
- */
- protected calculateXScalingFactor(systemFixWidth: number, systemVarWidth: number): number {
- if (Math.abs(systemVarWidth - 0) < 0.00001 || Math.abs(systemFixWidth - 0) < 0.00001) {
- return 1.0;
- }
- let systemEndX: number;
- const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
- systemEndX = currentSystem.StaffLines[0].PositionAndShape.Size.width;
- const scalingFactor: number = (systemEndX - systemFixWidth) / systemVarWidth;
- return scalingFactor;
- }
- /**
- * Stretch the whole System so that no white space is left at the end.
- * @param systemEndsPart
- */
- protected stretchMusicSystem(systemEndsPart: boolean): void {
- let scalingFactor: number = this.calculateXScalingFactor(
- this.currentSystemParams.currentSystemFixWidth, this.currentSystemParams.currentSystemVarWidth
- );
- if (systemEndsPart) {
- scalingFactor = Math.min(scalingFactor, this.rules.LastSystemMaxScalingFactor);
- }
- const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
- for (let visStaffIdx: number = 0, len: number = currentSystem.StaffLines.length; visStaffIdx < len; ++visStaffIdx) {
- const staffLine: StaffLine = currentSystem.StaffLines[visStaffIdx];
- let currentXPosition: number = 0.0;
- for (let measureIndex: number = 0; measureIndex < staffLine.Measures.length; measureIndex++) {
- const measure: GraphicalMeasure = staffLine.Measures[measureIndex];
- measure.setPositionInStaffline(currentXPosition);
- const beginInstructionsWidth: number = measure.beginInstructionsWidth;
- // if (measureIndex === 0 && measure.staffEntries) {
- // if (!measure.parentSourceMeasure.hasLyrics) {
- // beginInstructionsWidth *= 1; // TODO the first measure in a system is always slightly too big. why? try e.g. 0.6
- // }
- // }
- measure.setWidth(beginInstructionsWidth + measure.minimumStaffEntriesWidth * scalingFactor + measure.endInstructionsWidth);
- if (measureIndex < this.currentSystemParams.systemMeasures.length) {
- const startLine: SystemLinesEnum = this.currentSystemParams.systemMeasures[measureIndex].beginLine;
- const lineWidth: number = measure.getLineWidth(SystemLinesEnum.BoldThinDots);
- switch (startLine) {
- case SystemLinesEnum.BoldThinDots:
- let xPosition: number = currentXPosition;
- if (measureIndex === 0) {
- xPosition = currentXPosition + measure.beginInstructionsWidth - lineWidth;
- }
- currentSystem.createVerticalLineForMeasure(xPosition, lineWidth, startLine, SystemLinePosition.MeasureBegin, measureIndex, measure);
- break;
- default:
- }
- }
- measure.staffEntriesScaleFactor = scalingFactor;
- measure.layoutSymbols();
- const nextMeasureHasRepStartLine: boolean = measureIndex + 1 < this.currentSystemParams.systemMeasures.length
- && this.currentSystemParams.systemMeasures[measureIndex + 1].beginLine === SystemLinesEnum.BoldThinDots;
- if (!nextMeasureHasRepStartLine) {
- let endLine: SystemLinesEnum = SystemLinesEnum.SingleThin;
- if (measureIndex < this.currentSystemParams.systemMeasures.length) {
- endLine = this.currentSystemParams.systemMeasures[measureIndex].endLine;
- }
- const lineWidth: number = measure.getLineWidth(endLine);
- let xPos: number = measure.PositionAndShape.RelativePosition.x + measure.PositionAndShape.BorderRight - lineWidth;
- if (endLine === SystemLinesEnum.DotsBoldBoldDots) {
- xPos -= lineWidth / 2;
- }
- currentSystem.createVerticalLineForMeasure(xPos, lineWidth, endLine, SystemLinePosition.MeasureEnd, measureIndex, measure);
- }
- currentXPosition = measure.PositionAndShape.RelativePosition.x + measure.PositionAndShape.BorderRight;
- }
- }
- if (systemEndsPart) {
- this.decreaseMusicSystemBorders();
- }
- }
- /**
- * If the last [[MusicSystem]] doesn't need stretching, then this method decreases the System's Width,
- * the [[StaffLine]]'s Width and the 5 [[StaffLine]]s length.
- */
- protected decreaseMusicSystemBorders(): void {
- const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
- const bb: BoundingBox = CollectionUtil.last(currentSystem.StaffLines[0].Measures).PositionAndShape;
- const width: number = bb.RelativePosition.x + bb.Size.width;
- for (let idx: number = 0, len: number = currentSystem.StaffLines.length; idx < len; ++idx) {
- const staffLine: StaffLine = currentSystem.StaffLines[idx];
- staffLine.PositionAndShape.BorderRight = width;
- for (let idx2: number = 0, len2: number = staffLine.StaffLines.length; idx2 < len2; ++idx2) {
- const graphicalLine: GraphicalLine = staffLine.StaffLines[idx2];
- graphicalLine.End = new PointF2D(width, graphicalLine.End.y);
- }
- }
- currentSystem.PositionAndShape.BorderRight = width + this.currentSystemParams.maxLabelLength + this.rules.SystemLabelsRightMargin;
- }
- /**
- * This method updates the System's StaffLine's RelativePosition (starting from the given index).
- * @param musicSystem
- * @param index
- * @param value
- */
- protected updateStaffLinesRelativePosition(musicSystem: MusicSystem, index: number, value: number): void {
- for (let i: number = index; i < musicSystem.StaffLines.length; i++) {
- musicSystem.StaffLines[i].PositionAndShape.RelativePosition.y = value;
- }
- musicSystem.PositionAndShape.BorderBottom += value;
- }
- /**
- * Create a new [[GraphicalMusicPage]]
- * (for now only one long page is used per music sheet, as we scroll down and have no page flips)
- * @returns {GraphicalMusicPage}
- */
- protected createMusicPage(): GraphicalMusicPage {
- // const previousPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages.last();
- // const previousSizeY: number = previousPage ? previousPage.PositionAndShape.Size.height : 0;
- const page: GraphicalMusicPage = new GraphicalMusicPage(this.graphicalMusicSheet);
- this.graphicalMusicSheet.MusicPages.push(page);
- page.PageNumber = this.graphicalMusicSheet.MusicPages.length; // caution: page number = page index + 1
- page.PositionAndShape.BorderLeft = 0.0;
- page.PositionAndShape.BorderRight = this.graphicalMusicSheet.ParentMusicSheet.pageWidth;
- page.PositionAndShape.BorderTop = 0.0;
- page.PositionAndShape.BorderBottom = this.rules.PageHeight;
- page.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
- return page;
- }
- protected addSystemToPage(page: GraphicalMusicPage, system: MusicSystem): void {
- page.MusicSystems.push(system);
- system.Parent = page;
- }
- /**
- * This method checks the distances between any two consecutive StaffLines of a System and if needed, shifts the lower one down.
- * @param musicSystem
- */
- protected optimizeDistanceBetweenStaffLines(musicSystem: MusicSystem): void {
- // don't perform any y-spacing in case of a StaffEntryLink (in both StaffLines)
- if (!musicSystem.checkStaffEntriesForStaffEntryLink()) {
- for (let i: number = 0; i < musicSystem.StaffLines.length - 1; i++) {
- const upperBottomLine: number[] = musicSystem.StaffLines[i].BottomLine;
- const lowerSkyLine: number[] = musicSystem.StaffLines[i + 1].SkyLine;
- // 1. Find maximum required space for sky bottom line touching each other
- let maxDistance: number = 0;
- for (let j: number = 0; j < upperBottomLine.length; j++) {
- const bottomLineValue: number = upperBottomLine[j];
- // look at a range of +/- 2 Units to also ensure that objects are also not too close in x-direction:
- const startIdx: number = Math.max(0, j - 6);
- const endIdx: number = Math.min(lowerSkyLine.length - 1, j + 6);
- let skylineValue: number = 0;
- for (let lowerIdx: number = startIdx; lowerIdx <= endIdx; lowerIdx++) {
- skylineValue = Math.min(skylineValue, lowerSkyLine[lowerIdx]);
- }
- const distance: number = bottomLineValue - skylineValue;
- maxDistance = Math.max(distance, maxDistance);
- }
- // 2. Add user defined distance between sky bottom line
- maxDistance += this.rules.MinSkyBottomDistBetweenStaves;
- // 3. Take the maximum between previous value and user defined value for staff line minimum distance
- maxDistance = Math.max(maxDistance, this.rules.StaffHeight + this.rules.MinimumStaffLineDistance);
- const lowerStafflineYPos: number = maxDistance + musicSystem.StaffLines[i].PositionAndShape.RelativePosition.y;
- this.updateStaffLinesRelativePosition(musicSystem, i + 1, lowerStafflineYPos);
- }
- }
- const firstStaffLine: StaffLine = musicSystem.StaffLines[0];
- musicSystem.PositionAndShape.BorderTop = firstStaffLine.PositionAndShape.RelativePosition.y + firstStaffLine.PositionAndShape.BorderTop;
- const lastStaffLine: StaffLine = musicSystem.StaffLines[musicSystem.StaffLines.length - 1];
- musicSystem.PositionAndShape.BorderBottom = lastStaffLine.PositionAndShape.RelativePosition.y + lastStaffLine.PositionAndShape.BorderBottom;
- }
- /** Calculates the relative Positions of all MusicSystems.
- *
- */
- protected calculateMusicSystemsRelativePositions(): void {
- let currentPage: GraphicalMusicPage = this.createMusicPage();
- let currentYPosition: number = 0;
- // xPosition is always fixed
- let currentSystem: MusicSystem = this.musicSystems[0];
- let timesPageCouldntFitSingleSystem: number = 0;
- for (let i: number = 0; i < this.musicSystems.length; i++) {
- currentSystem = this.musicSystems[i];
- if (currentPage.MusicSystems.length === 0) {
- // if this is the first system on the current page:
- // take top margins into account
- this.addSystemToPage(currentPage, currentSystem);
- if (this.rules.CompactMode) {
- currentYPosition = this.rules.PageTopMarginNarrow;
- } else {
- currentYPosition = this.rules.PageTopMargin;
- }
- if (this.graphicalMusicSheet.MusicPages.length === 1) {
- /*
- Only need this in the event that lyricist or composer text intersects with title,
- which seems exceedingly rare.
- Leaving here just in case for future needs.
- Prefer to use skyline calculator in MusicSheetCalculator.calculatePageLabels
- let maxLineCount: number = this.graphicalMusicSheet.Composer?.TextLines?.length;
- let maxFontHeight: number = this.graphicalMusicSheet.Composer?.Label?.fontHeight;
- let lyricistLineCount: number = this.graphicalMusicSheet.Lyricist?.TextLines?.length;
- let lyricistFontHeight: number = this.graphicalMusicSheet.Lyricist?.Label?.fontHeight;
- maxLineCount = maxLineCount ? maxLineCount : 0;
- maxFontHeight = maxFontHeight ? maxFontHeight : 0;
- lyricistLineCount = lyricistLineCount ? lyricistLineCount : 0;
- lyricistFontHeight = lyricistFontHeight ? lyricistFontHeight : 0;
- let maxHeight: number = maxLineCount * maxFontHeight;
- const totalLyricist: number = lyricistLineCount * lyricistFontHeight;
- if (totalLyricist > maxHeight) {
- maxLineCount = lyricistLineCount;
- maxFontHeight = lyricistFontHeight;
- maxHeight = totalLyricist;
- } */
- if (this.rules.RenderTitle) {
- // if it is the first System on the FIRST page: Add Title height and gap-distance
- currentYPosition += this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
- this.rules.TitleBottomDistance;
- }
- /*
- see comment above - only needed for rare case of composer/lyricist being
- wide enough to be below the title (or title wide enough to be above)
- if (maxLineCount > 2) {
- currentYPosition += maxFontHeight * (maxLineCount - 2);
- }*/
- }
- // now add the border-top: everything that stands out above the staffline:
- currentYPosition += -currentSystem.PositionAndShape.BorderTop;
- const relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin,
- currentYPosition);
- currentSystem.PositionAndShape.RelativePosition = relativePosition;
- // check if the first system doesn't even fit on the page -> would lead to truncation at bottom end:
- if (currentYPosition + currentSystem.PositionAndShape.BorderBottom > this.rules.PageHeight - this.rules.PageBottomMargin) {
- // can't fit single system on page, maybe PageFormat too small
- timesPageCouldntFitSingleSystem++;
- if (timesPageCouldntFitSingleSystem <= 4) { // only warn once with detailed info
- console.log(`warning: could not fit a single system on page ${currentPage.PageNumber}` +
- ` and measure number ${currentSystem.GraphicalMeasures[0][0].MeasureNumber}.
- The PageFormat may be too small for this sheet."
- Will not give further warnings for all pages, only total.`
- );
- }
- }
- } else {
- // if this is not the first system on the page:
- // find optimum distance between Systems
- const previousSystem: MusicSystem = this.musicSystems[i - 1];
- const prevSystemLastStaffline: StaffLine = previousSystem.StaffLines[previousSystem.StaffLines.length - 1];
- const prevSystemLastStaffLineBB: BoundingBox = prevSystemLastStaffline.PositionAndShape;
- let distance: number = this.findRequiredDistanceWithSkyBottomLine(previousSystem, currentSystem);
- // make sure the optical distance is the user-defined min distance:
- distance += this.rules.MinSkyBottomDistBetweenSystems;
- distance = Math.max(distance, this.rules.MinimumDistanceBetweenSystems + prevSystemLastStaffline.StaffHeight);
- const newYPosition: number = currentYPosition +
- prevSystemLastStaffLineBB.RelativePosition.y +
- distance;
- // calculate the needed height for placing the current system on the page,
- // to see if it still fits:
- const currSystemBottomYPos: number = newYPosition +
- currentSystem.PositionAndShape.BorderMarginBottom;
- const doXmlPageBreak: boolean = this.rules.NewPageAtXMLNewPageAttribute && previousSystem.breaksPage;
- if (!doXmlPageBreak &&
- (currSystemBottomYPos < this.rules.PageHeight - this.rules.PageBottomMargin)) {
- // enough space on this page:
- this.addSystemToPage(currentPage, currentSystem);
- currentYPosition = newYPosition;
- const relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin,
- currentYPosition);
- currentSystem.PositionAndShape.RelativePosition = relativePosition;
- } else {
- // new page needed:
- currentPage = this.createMusicPage();
- // re-check this system again:
- i -= 1;
- continue;
- }
- }
- }
- if (timesPageCouldntFitSingleSystem > 0) {
- console.log(`total amount of pages that couldn't fit a single music system: ${timesPageCouldntFitSingleSystem} of ${currentPage.PageNumber}`);
- }
- if (this.rules.PageBottomExtraWhiteSpace > 0 && this.graphicalMusicSheet.MusicPages.length === 1) {
- // experimental, not used unless the EngravingRule is set to > 0 (default 0)
- // calculate last page's bounding box, otherwise it uses this.rules.PageHeight which is 10001
- currentPage.PositionAndShape.calculateBoundingBox();
- // TODO currently bugged with PageFormat A3. this squeezes lyrics and notes (with A3 Landscape). why?
- // for this reason, the extra white space should currently only be used with the Endless PageFormat,
- // and using EngravingRules.PageBottomExtraWhiteSpace should be considered experimental.
- // add this.rules.PageBottomMargin
- const pageBottomMarginBB: BoundingBox = new BoundingBox(currentPage, currentPage.PositionAndShape, false);
- // pageBottomMarginBB.RelativePosition.x = 0;
- pageBottomMarginBB.RelativePosition.y = currentPage.PositionAndShape.BorderMarginBottom;
- // pageBottomMarginBB.BorderBottom = this.rules.PageBottomMargin;
- pageBottomMarginBB.BorderBottom = this.rules.PageBottomExtraWhiteSpace;
- pageBottomMarginBB.calculateBoundingBox();
- currentPage.PositionAndShape.calculateBoundingBox();
- }
- }
- /**
- * Finds the minimum required distance between two systems
- * with the help of the sky- and bottom lines
- * @param upperSystem
- * @param lowerSystem
- */
- private findRequiredDistanceWithSkyBottomLine(upperSystem: MusicSystem, lowerSystem: MusicSystem): number {
- const upperSystemLastStaffLine: StaffLine = upperSystem.StaffLines[upperSystem.StaffLines.length - 1];
- const lowerSystemFirstStaffLine: StaffLine = lowerSystem.StaffLines[0];
- const upperBottomLineArray: number[] = upperSystemLastStaffLine.BottomLine;
- const lowerSkyLineArray: number[] = lowerSystemFirstStaffLine.SkyLine;
- const upperStaffLineBB: BoundingBox = upperSystemLastStaffLine.PositionAndShape;
- const lowerStaffLineBB: BoundingBox = lowerSystemFirstStaffLine.PositionAndShape;
- const skylinePixelWidth: number = 1 / this.rules.SamplingUnit;
- // Find maximum required space for sky and bottom line touching each other
- let maxDistance: number = 0;
- for (let upperIdx: number = 0; upperIdx < upperBottomLineArray.length; upperIdx++) {
- const bottomLineValue: number = upperBottomLineArray[upperIdx];
- // find index of the same x-position in lower skyline:
- const lowerCenterIdx: number = upperIdx +
- Math.round((upperStaffLineBB.RelativePosition.x - lowerStaffLineBB.RelativePosition.x) * skylinePixelWidth);
- if (lowerCenterIdx < 0) {
- // should actually not happen..
- continue;
- }
- if (lowerCenterIdx >= lowerSkyLineArray.length) {
- // lower system ends earlier x-wise than upper system (e.g. at last system, if it is not stretched)
- break;
- }
- // look at a range of +/- 2 Units to also ensure that objects are also not too close in x-direction:
- const startIdx: number = Math.max(0, lowerCenterIdx - 6);
- const endIdx: number = Math.min(lowerSkyLineArray.length - 1, lowerCenterIdx + 6);
- let skylineValue: number = 0;
- for (let lowerIdx: number = startIdx; lowerIdx <= endIdx; lowerIdx++) {
- skylineValue = Math.min(skylineValue, lowerSkyLineArray[lowerIdx]);
- }
- const distance: number = bottomLineValue - skylineValue;
- maxDistance = Math.max(distance, maxDistance);
- }
- if (maxDistance === 0) {
- // can only happen when the bottom- and skyline have no x-overlap at all:
- // fall back to borders:
- maxDistance = upperStaffLineBB.BorderBottom - lowerStaffLineBB.BorderTop;
- }
- return maxDistance;
- }
- }
- export class SystemBuildParameters {
- public currentSystem: MusicSystem;
- public systemMeasures: MeasureBuildParameters[] = [];
- public systemMeasureIndex: number = 0;
- public currentWidth: number = 0;
- public currentSystemFixWidth: number = 0;
- public currentSystemVarWidth: number = 0;
- public maxLabelLength: number = 0;
- public IsSystemStartMeasure(): boolean {
- return this.systemMeasureIndex === 0;
- }
- }
- export class MeasureBuildParameters {
- public beginLine: SystemLinesEnum;
- public endLine: SystemLinesEnum;
- }
|