/// <summary> /// Moves this horizontal beamBlock vertically until it is on the right side (above or below) /// the noteheads, and as close as possible to the noteheads. /// If there is only a single (quaver) beam, it ends up with its inner edge one octave from /// the OriginY of the closest notehead in the group. /// Otherwise the smallest distance between any beam and the closest notehead will be a sixth. /// </summary> /// <returns></returns> private void MoveToNoteheads(List <ChordMetrics> chordsMetrics, Dictionary <DurationClass, float> durationClassBeamThickness) { float staffOriginY = Chords[0].Voice.Staff.Metrics.OriginY; if (_stemDirection == VerticalDir.down) { float lowestBottom = float.MinValue; for (int i = 0; i < Chords.Count; ++i) { ChordMetrics chordMetrics = chordsMetrics[i]; HeadMetrics bottomHeadMetrics = chordMetrics.BottomHeadMetrics; float beamBottom = bottomHeadMetrics.OriginY + durationClassBeamThickness[Chords[i].DurationClass]; if (((bottomHeadMetrics.OriginY - staffOriginY) % _gap) != 0) { beamBottom += (_gap * 2.5F); } else { beamBottom += (_gap * 2.65F); } lowestBottom = lowestBottom > beamBottom ? lowestBottom : beamBottom; } if (Beams.Count == 1) // only a quaver beam { lowestBottom += _gap; } Move(lowestBottom - staffOriginY); } else // stems up { float highestTop = float.MaxValue; for (int i = 0; i < Chords.Count; ++i) { ChordMetrics chordMetrics = chordsMetrics[i]; HeadMetrics topHeadMetrics = chordMetrics.TopHeadMetrics; float beamTop = topHeadMetrics.OriginY - durationClassBeamThickness[Chords[i].DurationClass]; if (((topHeadMetrics.OriginY - staffOriginY) % _gap) != 0) { beamTop -= (_gap * 2.5F); } else { beamTop -= (_gap * 2.65F); } highestTop = highestTop < beamTop ? highestTop : beamTop; } if (Beams.Count == 1) // only a quaver beam { highestTop -= _gap; } Move(highestTop - staffOriginY); } }
/// <summary> /// Used when creating temporary heads for chord alignment purposes. /// </summary> public HeadMetrics(HeadMetrics otherHead, DurationClass durationClass) : base(durationClass, false, otherHead.FontHeight) { // move to position of other head Move(otherHead.OriginX - _originX, otherHead.OriginY - OriginY); float horizontalPadding = otherHead.FontHeight * 0.04F; _leftStemX = _left; _rightStemX = _right; _left -= horizontalPadding; _right += horizontalPadding; }
/// <summary> /// Used when creating temporary heads for chord alignment purposes. /// </summary> public HeadMetrics(HeadMetrics otherHead, DurationClass durationClass) : base(durationClass, otherHead.FontHeight, CSSObjectClass.none) { // move to position of other head Move(otherHead.OriginX - _originX, otherHead.OriginY - OriginY); double horizontalPadding = otherHead.FontHeight * 0.04; _leftStemX = _left; _rightStemX = _right; _left -= horizontalPadding; _right += horizontalPadding; }
/// <summary> /// Notehead metrics.Left and metrics.Right include horizontal padding, /// so head overlaps cannot be checked using the standard Metrics.Overlaps function. /// </summary> public bool OverlapsHead(HeadMetrics otherHeadMetrics) { // See the above constructor. Sorry, I didnt want to save the value in every Head! float thisHorizontalPadding = this._fontHeight * 0.04F; float thisRealLeft = _left + thisHorizontalPadding; float thisRealRight = _right - thisHorizontalPadding; float otherHorizontalPadding = otherHeadMetrics.FontHeight * 0.04F; float otherRealLeft = otherHeadMetrics.Left + thisHorizontalPadding; float otherRealRight = otherHeadMetrics.Right - thisHorizontalPadding; bool verticalOverlap = this.Bottom >= otherHeadMetrics.Top && this.Top <= otherHeadMetrics.Bottom; bool horizontalOverlap = thisRealRight >= otherRealLeft && thisRealLeft <= otherRealRight; return(verticalOverlap && horizontalOverlap); }
/// <summary> /// The distance between the inner edge of this beamBlock and the headmetrics. /// This is a positive value /// a) if the _stemDirection is VerticalDir.up and the headMetrics is completely below this beamBlock, /// or b) if the _stemDirection is VerticalDir.down and the headMetrics is completely above this beamBlock. /// </summary> private float VerticalDistanceToHead(HeadMetrics headMetrics, float headMsPosition) { float headX = headMetrics.OriginX; float headY = headMetrics.Top; float minDistanceToHead = float.MaxValue; float tanA = this.TanAngle; if (_stemDirection == VerticalDir.up) { float beamBottomAtHeadY; foreach (Beam beam in Beams) { float beamBeginMsPosition = BeamBeginMsPosition(beam); float beamEndMsPosition = BeamEndMsPosition(beam); if (beamBeginMsPosition <= headMsPosition && beamEndMsPosition >= headMsPosition) { beamBottomAtHeadY = beam.LeftTopY - ((headX - beam.LeftX) * tanA) + _beamThickness; float distanceToHead = headY - beamBottomAtHeadY; minDistanceToHead = minDistanceToHead < distanceToHead ? minDistanceToHead : distanceToHead; } } } else // _stemDirection == VerticalDir.down { headY = headMetrics.Bottom; float beamTopAtHeadY; foreach (Beam beam in Beams) { float beamBeginMsPosition = BeamBeginMsPosition(beam); float beamEndMsPosition = BeamEndMsPosition(beam); if (beamBeginMsPosition <= headMsPosition && beamEndMsPosition >= headMsPosition) { beamTopAtHeadY = beam.LeftTopY - ((headX - beam.LeftX) * tanA); float distanceToHead = beamTopAtHeadY - headY; minDistanceToHead = minDistanceToHead < distanceToHead ? minDistanceToHead : distanceToHead; } } } return(minDistanceToHead); }
/// <summary> /// The distance between the inner edge of this beamBlock and the headmetrics. /// This is a positive value /// a) if the _stemDirection is VerticalDir.up and the headMetrics is completely below this beamBlock, /// or b) if the _stemDirection is VerticalDir.down and the headMetrics is completely above this beamBlock. /// </summary> private float VerticalDistanceToHead(HeadMetrics headMetrics, float headMsPosition) { float headX = headMetrics.OriginX; float headY = headMetrics.Top; float minDistanceToHead = float.MaxValue; float tanA = this.TanAngle; if(_stemDirection == VerticalDir.up) { float beamBottomAtHeadY; foreach(Beam beam in Beams) { float beamBeginMsPosition = BeamBeginMsPosition(beam); float beamEndMsPosition = BeamEndMsPosition(beam); if(beamBeginMsPosition <= headMsPosition && beamEndMsPosition >= headMsPosition) { beamBottomAtHeadY = beam.LeftTopY - ((headX - beam.LeftX) * tanA) + _beamThickness; float distanceToHead = headY - beamBottomAtHeadY; minDistanceToHead = minDistanceToHead < distanceToHead ? minDistanceToHead : distanceToHead; } } } else // _stemDirection == VerticalDir.down { headY = headMetrics.Bottom; float beamTopAtHeadY; foreach(Beam beam in Beams) { float beamBeginMsPosition = BeamBeginMsPosition(beam); float beamEndMsPosition = BeamEndMsPosition(beam); if(beamBeginMsPosition <= headMsPosition && beamEndMsPosition >= headMsPosition) { beamTopAtHeadY = beam.LeftTopY - ((headX - beam.LeftX) * tanA); float distanceToHead = beamTopAtHeadY - headY; minDistanceToHead = minDistanceToHead < distanceToHead ? minDistanceToHead : distanceToHead; } } } return minDistanceToHead; }
/// <summary> /// chord.Heads are in top-down order. /// </summary> private void SetHeadsMetrics(ChordSymbol chord, float ledgerlineStemStrokeWidth) { _headsMetricsTopDown = new List<HeadMetrics>(); HeadMetrics hMetrics = new HeadMetrics(chord, null, _gap); // the head is horizontally aligned at 0 by default. float horizontalShift = hMetrics.RightStemX - hMetrics.LeftStemX - (ledgerlineStemStrokeWidth / 2F); // the distance to shift left or right if heads would collide float shiftRange = _gap * 0.75F; if(chord.Stem.Direction == VerticalDir.up) { List<Head> bottomUpHeads = new List<Head>(); foreach(Head head in chord.HeadsTopDown) bottomUpHeads.Insert(0, head); List<HeadMetrics> bottomUpMetrics = new List<HeadMetrics>(); foreach(Head head in bottomUpHeads) { float newHeadOriginY = head.GetOriginY(_clef, _gap); // note that the CHORD's originY is always at the top line of the staff float newHeadAlignX = 0F; foreach(Metrics headMetric in bottomUpMetrics) { float existingHeadAlignX = (headMetric.Left + headMetric.Right) / 2F; if((newHeadOriginY == headMetric.OriginY) || (existingHeadAlignX == 0F && newHeadAlignX < (existingHeadAlignX + horizontalShift) && newHeadOriginY > (headMetric.OriginY - shiftRange))) { newHeadAlignX = existingHeadAlignX + horizontalShift; // shifts more than once for extreme clusters ( e.g. F,F#,G) } else newHeadAlignX = 0; } HeadMetrics headMetrics = new HeadMetrics(chord, head, _gap); headMetrics.Move(newHeadAlignX, newHeadOriginY); // moves head.originY to headY bottomUpMetrics.Add(headMetrics); } for(int i = bottomUpMetrics.Count - 1; i >= 0; --i) { _headsMetricsTopDown.Add(bottomUpMetrics[i]); } } else // stem is down { foreach(Head head in chord.HeadsTopDown) { float newHeadOriginY = head.GetOriginY(_clef, _gap); // note that the CHORD's originY is always at the top line of the staff float newHeadAlignX = 0F; foreach(HeadMetrics headMetric in _headsMetricsTopDown) { float existingHeadAlignX = (headMetric.Left + headMetric.Right) / 2F; if((newHeadOriginY == headMetric.OriginY) || (existingHeadAlignX == 0F && newHeadAlignX < (existingHeadAlignX + horizontalShift) && newHeadOriginY < (headMetric.OriginY + shiftRange))) { newHeadAlignX -= horizontalShift; // can shift left more than once } else newHeadAlignX = 0; } HeadMetrics headMetrics = new HeadMetrics(chord, head, _gap); headMetrics.Move(newHeadAlignX, newHeadOriginY); // moves head.originY to headY _headsMetricsTopDown.Add(headMetrics); } } Debug.Assert(_originX == 0F); Debug.Assert(_headsMetricsTopDown.Count == chord.HeadsTopDown.Count); }
/// <summary> /// Returns the stem which the otherChordTopDownHeadsMetrics would have if their duration class was crotchet. /// DummyStemMetrics are used when aligning synchronous chords. /// </summary> private StemMetrics DummyStemMetrics( List<HeadMetrics> otherChordTopDownHeadsMetrics, VerticalDir stemDirection, float fontHeight, Metrics flagsBlockMetrics, BeamBlock beamBlock, float strokeWidth) { List<HeadMetrics> tempTopDownHeadsMetrics = new List<HeadMetrics>(); foreach(HeadMetrics headMetrics in otherChordTopDownHeadsMetrics) { HeadMetrics newHeadMetrics = new HeadMetrics(headMetrics, DurationClass.crotchet); tempTopDownHeadsMetrics.Add(newHeadMetrics); } return NewStemMetrics(tempTopDownHeadsMetrics, stemDirection, fontHeight, flagsBlockMetrics, beamBlock, strokeWidth); }
/// <summary> /// Notehead metrics.Left and metrics.Right include horizontal padding, /// so head overlaps cannot be checked using the standard Metrics.Overlaps function. /// </summary> public bool OverlapsHead(HeadMetrics otherHeadMetrics) { // See the above constructor. Sorry, I didnt want to save the value in every Head! float thisHorizontalPadding = this._fontHeight * 0.04F; float thisRealLeft = _left + thisHorizontalPadding; float thisRealRight = _right - thisHorizontalPadding; float otherHorizontalPadding = otherHeadMetrics.FontHeight * 0.04F; float otherRealLeft = otherHeadMetrics.Left + thisHorizontalPadding; float otherRealRight = otherHeadMetrics.Right - thisHorizontalPadding; bool verticalOverlap = this.Bottom >= otherHeadMetrics.Top && this.Top <= otherHeadMetrics.Bottom; bool horizontalOverlap = thisRealRight >= otherRealLeft && thisRealLeft <= otherRealRight; return verticalOverlap && horizontalOverlap; }