diff --git a/src.compiler/csharp/CSharpAstTransformer.ts b/src.compiler/csharp/CSharpAstTransformer.ts index 275b68c26..4b561739b 100644 --- a/src.compiler/csharp/CSharpAstTransformer.ts +++ b/src.compiler/csharp/CSharpAstTransformer.ts @@ -2526,12 +2526,20 @@ export default class CSharpAstTransformer { expression: {} as cs.Expression } as cs.InvocationExpression; + const parts = expression.text.split('/'); csExpr.expression = this.makeMemberAccess(csExpr, 'AlphaTab.Core.TypeHelper', 'CreateRegex'); csExpr.arguments.push({ parent: csExpr, nodeType: cs.SyntaxKind.StringLiteral, tsNode: expression, - text: expression.text + text: parts[1] + } as cs.StringLiteral); + + csExpr.arguments.push({ + parent: csExpr, + nodeType: cs.SyntaxKind.StringLiteral, + tsNode: expression, + text: parts[2] } as cs.StringLiteral); return csExpr; diff --git a/src.csharp/AlphaTab/Core/EcmaScript/RegExp.cs b/src.csharp/AlphaTab/Core/EcmaScript/RegExp.cs index ccdd6d348..7614daf8b 100644 --- a/src.csharp/AlphaTab/Core/EcmaScript/RegExp.cs +++ b/src.csharp/AlphaTab/Core/EcmaScript/RegExp.cs @@ -1,19 +1,57 @@ -using System.Text.RegularExpressions; +using System.Collections.Concurrent; +using System.Text.RegularExpressions; namespace AlphaTab.Core.EcmaScript { public class RegExp { + private static ConcurrentDictionary<(string pattern, string flags), RegExp> Cache = + new ConcurrentDictionary<(string pattern, string flags), RegExp>(); + private readonly Regex _regex; + private readonly bool _global; - public RegExp(string regex) + public RegExp(string regex, string flags = "") { - _regex = new Regex(regex, RegexOptions.Compiled); + if (!Cache.TryGetValue((regex, flags), out var cached)) + { + var netFlags = RegexOptions.Compiled; + foreach (var c in flags) + { + switch (c) + { + case 'i': + netFlags |= RegexOptions.IgnoreCase; + break; + case 'g': + _global = true; + break; + case 'm': + netFlags |= RegexOptions.Multiline; + break; + } + } + + _regex = new Regex(regex, netFlags); + Cache[(regex, flags)] = this; + } + else + { + _regex = cached._regex; + _global = cached._global; + } } public bool Exec(string s) { return _regex.IsMatch(s); } + + public string Replace(string input, string replacement) + { + return _global + ? _regex.Replace(input, replacement) + : _regex.Replace(input, replacement, 1); + } } } diff --git a/src.csharp/AlphaTab/Core/TypeHelper.cs b/src.csharp/AlphaTab/Core/TypeHelper.cs index d97473d84..483199156 100644 --- a/src.csharp/AlphaTab/Core/TypeHelper.cs +++ b/src.csharp/AlphaTab/Core/TypeHelper.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using AlphaTab.Core.EcmaScript; -using AlphaTab.Rendering.Glyphs; -using String = System.String; namespace AlphaTab.Core { @@ -273,6 +270,18 @@ public static string ToString(this double num, int radix) return num.ToString(CultureInfo.InvariantCulture); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RegExp CreateRegex(string pattern, string flags) + { + return new RegExp(pattern, flags); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Replace(this string input, RegExp pattern, string replacement) + { + return pattern.Replace(input, replacement); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsTruthy(string? s) { diff --git a/src/platform/svg/SvgCanvas.ts b/src/platform/svg/SvgCanvas.ts index 28779bfe4..e279c9f07 100644 --- a/src/platform/svg/SvgCanvas.ts +++ b/src/platform/svg/SvgCanvas.ts @@ -22,9 +22,8 @@ export abstract class SvgCanvas implements ICanvas { public settings!: Settings; public beginRender(width: number, height: number): void { - this.buffer = `\n`; + this.buffer = `\n`; this._currentPath = ''; this._currentPathIsEmpty = true; } @@ -137,10 +136,19 @@ export abstract class SvgCanvas implements ICanvas { if (this.textAlign !== TextAlign.Left) { s += ` text-anchor="${this.getSvgTextAlignment(this.textAlign)}"`; } - s += `>${text}`; + s += `>${SvgCanvas.escapeText(text)}`; this.buffer += s; } + private static escapeText(text: string) { + return text + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(//g, '>'); + } + protected getSvgTextAlignment(textAlign: TextAlign): string { switch (textAlign) { case TextAlign.Left: diff --git a/src/rendering/ScoreBarRenderer.ts b/src/rendering/ScoreBarRenderer.ts index 0e654a6ed..29038cfd0 100644 --- a/src/rendering/ScoreBarRenderer.ts +++ b/src/rendering/ScoreBarRenderer.ts @@ -29,7 +29,7 @@ import { ScoreBeatContainerGlyph } from '@src/rendering/ScoreBeatContainerGlyph' import { ScoreRenderer } from '@src/rendering/ScoreRenderer'; import { AccidentalHelper } from '@src/rendering/utils/AccidentalHelper'; import { BeamDirection } from '@src/rendering/utils/BeamDirection'; -import { BeamingHelper } from '@src/rendering/utils/BeamingHelper'; +import { BeamingHelper, BeamingHelperDrawInfo } from '@src/rendering/utils/BeamingHelper'; import { RenderingResources } from '@src/RenderingResources'; import { Settings } from '@src/Settings'; import { ModelUtils } from '@src/model/ModelUtils'; @@ -354,71 +354,104 @@ export class ScoreBarRenderer extends BarRendererBase { private calculateBeamYWithDirection(h: BeamingHelper, x: number, direction: BeamDirection): number { let stemSize: number = this.getStemSize(h); - const firstBeat = h.beats[0]; + if (!h.drawingInfos.has(direction)) { + let drawingInfo = new BeamingHelperDrawInfo(); + h.drawingInfos.set(direction, drawingInfo); - // create a line between the min and max note of the group - if (h.beats.length === 1) { - if (direction === BeamDirection.Up) { - return this.getScoreY(this.accidentalHelper.getMinLine(firstBeat)) - stemSize; - } - return this.getScoreY(this.accidentalHelper.getMaxLine(firstBeat)) + stemSize; - } + // the beaming logic works like this: + // 1. we take the first and last note, add the stem, and put a diagnal line between them. + // 2. the height of the diagonal line must not exceed a max height, + // - if this is the case, the line on the more distant note just gets longer + // 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps - const lastBeat = h.beats[h.beats.length - 1]; + const firstBeat = h.beats[0]; + const lastBeat = h.beats[h.beats.length - 1]; - // we use the min/max notes to place the beam along their real position - // we only want a maximum of 10 offset for their gradient - let maxDistance: number = 10 * this.scale; - // if the min note is not first or last, we can align notes directly to the position - // of the min note - const beatOfLowestNote = h.beatOfLowestNote; - const beatOfHighestNote = h.beatOfHighestNote; - if ( - direction === BeamDirection.Down && - beatOfLowestNote !== firstBeat && - beatOfLowestNote !== lastBeat - ) { - return this.getScoreY(this.accidentalHelper.getMaxLine(beatOfLowestNote)) + stemSize; - } - if ( - direction === BeamDirection.Up && - beatOfHighestNote !== firstBeat && - beatOfHighestNote !== lastBeat - ) { - return this.getScoreY(this.accidentalHelper.getMinLine(beatOfHighestNote)) - stemSize; - } + // 1. put direct diagonal line. + drawingInfo.startX = h.getBeatLineX(firstBeat); + drawingInfo.startY = + direction === BeamDirection.Up + ? this.getScoreY(this.accidentalHelper.getMinLine(firstBeat)) - stemSize + : this.getScoreY(this.accidentalHelper.getMaxLine(firstBeat)) + stemSize; - let startX: number = h.getBeatLineX(firstBeat); - let startY: number = - direction === BeamDirection.Up - ? this.getScoreY(this.accidentalHelper.getMinLine(firstBeat)) - stemSize - : this.getScoreY(this.accidentalHelper.getMaxLine(firstBeat)) + stemSize; + drawingInfo.endX = h.getBeatLineX(lastBeat); + drawingInfo.endY = + direction === BeamDirection.Up + ? this.getScoreY(this.accidentalHelper.getMinLine(lastBeat)) - stemSize + : this.getScoreY(this.accidentalHelper.getMaxLine(lastBeat)) + stemSize; + + // 2. ensure max height + // we use the min/max notes to place the beam along their real position + // we only want a maximum of 10 offset for their gradient + let maxDistance: number = 10 * this.scale; + if (direction === BeamDirection.Down && drawingInfo.startY > drawingInfo.endY && drawingInfo.startY - drawingInfo.endY > maxDistance) { + drawingInfo.endY = drawingInfo.startY - maxDistance; + } + if (direction === BeamDirection.Down && drawingInfo.endY > drawingInfo.startY && drawingInfo.endY - drawingInfo.startY > maxDistance) { + drawingInfo.startY = drawingInfo.endY - maxDistance; + } + if (direction === BeamDirection.Up && drawingInfo.startY < drawingInfo.endY && drawingInfo.endY - drawingInfo.startY > maxDistance) { + drawingInfo.endY = drawingInfo.startY + maxDistance; + } + if (direction === BeamDirection.Up && drawingInfo.endY < drawingInfo.startY && drawingInfo.startY - drawingInfo.endY > maxDistance) { + drawingInfo.startY = drawingInfo.endY + maxDistance; + } - let endX: number = h.getBeatLineX(lastBeat); - let endY: number = - direction === BeamDirection.Up - ? this.getScoreY(this.accidentalHelper.getMinLine(lastBeat)) - stemSize - : this.getScoreY(this.accidentalHelper.getMaxLine(lastBeat)) + stemSize; + // 3. let middle elements shift up/down + if (h.beats.length > 1) { + // check if highest note shifts bar up or down + if (direction === BeamDirection.Up) { + let yNeededForHighestNote = this.getScoreY(this.accidentalHelper.getMinLine(h.beatOfHighestNote)) - stemSize; + const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfHighestNote)); + + const diff = yGivenByCurrentValues - yNeededForHighestNote; + if (diff > 0) { + drawingInfo.startY -= diff; + drawingInfo.endY -= diff; + } + } else { + let yNeededForLowestNote = this.getScoreY(this.accidentalHelper.getMaxLine(h.beatOfLowestNote)) + stemSize; + const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfLowestNote)); + + const diff = yNeededForLowestNote - yGivenByCurrentValues; + if (diff > 0) { + drawingInfo.startY += diff; + drawingInfo.endY += diff; + } + } - // ensure the maxDistance - if (direction === BeamDirection.Down && startY > endY && startY - endY > maxDistance) { - endY = startY - maxDistance; - } - if (direction === BeamDirection.Down && endY > startY && endY - startY > maxDistance) { - startY = endY - maxDistance; - } - if (direction === BeamDirection.Up && startY < endY && endY - startY > maxDistance) { - endY = startY + maxDistance; - } - if (direction === BeamDirection.Up && endY < startY && startY - endY > maxDistance) { - startY = endY + maxDistance; - } - // get the y position of the given beat on this curve - if (startX === endX) { - return startY; + // check if rest shifts bar up or down + if (h.minRestLine !== null || h.maxRestLine !== null) { + const barCount: number = ModelUtils.getIndex(h.shortestDuration) - 2; + let scaleMod: number = h.isGrace ? NoteHeadGlyph.GraceScale : 1; + let barSpacing: number = barCount * + (BarRendererBase.BeamSpacing + BarRendererBase.BeamThickness) * this.scale * scaleMod; + barSpacing += BarRendererBase.BeamSpacing; + + if (direction === BeamDirection.Up && h.minRestLine !== null) { + let yNeededForRest = this.getScoreY(h.minRestLine!) - barSpacing; + const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfMinRestLine!)); + + const diff = yGivenByCurrentValues - yNeededForRest; + if (diff > 0) { + drawingInfo.startY -= diff; + drawingInfo.endY -= diff; + } + } else if (direction === BeamDirection.Down && h.maxRestLine !== null) { + let yNeededForRest = this.getScoreY(h.maxRestLine!) + barSpacing; + const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(h.beatOfMaxRestLine!)); + + const diff = yNeededForRest - yGivenByCurrentValues; + if (diff > 0) { + drawingInfo.startY += diff; + drawingInfo.endY += diff; + } + } + } + } } - // y(x) = ( (y2 - y1) / (x2 - x1) ) * (x - x1) + y1; - return ((endY - startY) / (endX - startX)) * (x - startX) + startY; + + return h.drawingInfos.get(direction)!.calcY(x); } diff --git a/src/rendering/glyphs/ScoreBeatGlyph.ts b/src/rendering/glyphs/ScoreBeatGlyph.ts index d1d9a8419..67e568fbf 100644 --- a/src/rendering/glyphs/ScoreBeatGlyph.ts +++ b/src/rendering/glyphs/ScoreBeatGlyph.ts @@ -82,8 +82,8 @@ export class ScoreBeatGlyph extends BeatOnNoteGlyphBase { 0, 0, 4 * - (this.container.beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1) * - this.scale + (this.container.beat.graceType !== GraceType.None ? NoteHeadGlyph.GraceScale : 1) * + this.scale ) ); this.addGlyph(ghost); @@ -129,6 +129,10 @@ export class ScoreBeatGlyph extends BeatOnNoteGlyphBase { this.restGlyph.beat = this.container.beat; this.restGlyph.beamingHelper = this.beamingHelper; this.addGlyph(this.restGlyph); + if (this.beamingHelper) { + this.beamingHelper.applyRest(this.container.beat, line); + } + // // Note dots // diff --git a/src/rendering/utils/AccidentalHelper.ts b/src/rendering/utils/AccidentalHelper.ts index 83ca32a74..bf9e14cf6 100644 --- a/src/rendering/utils/AccidentalHelper.ts +++ b/src/rendering/utils/AccidentalHelper.ts @@ -265,7 +265,7 @@ export class AccidentalHelper { const previousRenderer = this._barRenderer.previousRenderer as ScoreBarRenderer; if (previousRenderer) { const tieOriginLine = previousRenderer.accidentalHelper.getNoteLine(note.tieOrigin!); - if(tieOriginLine === line) { + if (tieOriginLine === line) { skipAccidental = true; } } @@ -328,26 +328,29 @@ export class AccidentalHelper { } if (!isHelperNote) { - let lines: BeatLines; - if (this._beatLines.has(relatedBeat.id)) { - lines = this._beatLines.get(relatedBeat.id)!; - } - else { - lines = new BeatLines(); - this._beatLines.set(relatedBeat.id, lines); - } - - if (lines.minLine === -1000 || line < lines.minLine) { - lines.minLine = line; - } - if (lines.minLine === -1000 || line > lines.maxLine) { - lines.maxLine = line; - } + this.registerLine(relatedBeat, line); } return accidentalToSet; } + private registerLine(relatedBeat: Beat, line: number) { + let lines: BeatLines; + if (this._beatLines.has(relatedBeat.id)) { + lines = this._beatLines.get(relatedBeat.id)!; + } + else { + lines = new BeatLines(); + this._beatLines.set(relatedBeat.id, lines); + } + if (lines.minLine === -1000 || line < lines.minLine) { + lines.minLine = line; + } + if (lines.minLine === -1000 || line > lines.maxLine) { + lines.maxLine = line; + } + } + public getMaxLine(b: Beat): number { return this._beatLines.has(b.id) ? this._beatLines.get(b.id)!.maxLine diff --git a/src/rendering/utils/BeamingHelper.ts b/src/rendering/utils/BeamingHelper.ts index c7c6fd083..31f470842 100644 --- a/src/rendering/utils/BeamingHelper.ts +++ b/src/rendering/utils/BeamingHelper.ts @@ -19,6 +19,29 @@ class BeatLinePositions { public down: number = 0; } +export class BeamingHelperDrawInfo { + public startX: number = 0; + public startY: number = 0; + + public endX: number = 0; + public endY: number = 0; + + // + /** + * calculates the Y-position given a X-pos using the current start end point + * @param x + */ + public calcY(x: number): number { + // get the y position of the given beat on this curve + if (this.startX === this.endX) { + return this.startY; + } + + // y(x) = ( (y2 - y1) / (x2 - x1) ) * (x - x1) + y1; + return ((this.endY - this.startY) / (this.endX - this.startX)) * (x - this.startX) + this.startY; + } +} + /** * This public class helps drawing beams and bars for notes. */ @@ -26,6 +49,7 @@ export class BeamingHelper { private _staff: Staff; private _beatLineXPositions: Map = new Map(); private _renderer: BarRendererBase; + private _lastNonRestBeat: Beat | null = null; public voice: Voice | null = null; public beats: Beat[] = []; @@ -41,16 +65,9 @@ export class BeamingHelper { */ public hasTuplet: boolean = false; - private _firstBeatLowestNote: Note | null = null; private _firstBeatLowestNoteCompareValue: number = -1; - - private _firstBeatHighestNote: Note | null = null; private _firstBeatHighestNoteCompareValue: number = -1; - - private _lastBeatLowestNote: Note | null = null; private _lastBeatLowestNoteCompareValue: number = -1; - - private _lastBeatHighestNote: Note | null = null; private _lastBeatHighestNoteCompareValue: number = -1; private _lowestNoteInHelper: Note | null = null; @@ -63,6 +80,12 @@ export class BeamingHelper { public preferredBeamDirection: BeamDirection | null = null; public isGrace: boolean = false; + public minRestLine: number | null = null; + public beatOfMinRestLine: Beat | null = null; + + public maxRestLine: number | null = null; + public beatOfMaxRestLine: Beat | null = null; + public get hasLine(): boolean { return this.beats.length === 1 && this.beats[0].duration > Duration.Whole; } @@ -148,6 +171,78 @@ export class BeamingHelper { return this.invert(this._renderer.middleYPosition < avg ? BeamDirection.Up : BeamDirection.Down); } + /** + * Registers a rest beat within the accidental helper so the rest + * symbol is considered properly during beaming. + * @param beat The rest beat. + * @param line The line on which the rest symbol is placed + */ + public applyRest(beat: Beat, line: number): void { + // do not accept rests after the last beat which has notes + if (this._lastNonRestBeat && beat.index >= this._lastNonRestBeat.index) { + return; + } + + // correct the line of the glyph to a note which would + // be placed at the upper / lower end of the glyph. + let aboveRest = line; + let belowRest = line; + switch (beat.duration) { + case Duration.QuadrupleWhole: + aboveRest -= 2; + belowRest += 2; + break; + case Duration.DoubleWhole: + aboveRest -= 2; + belowRest -= 2; + break; + case Duration.Whole: + aboveRest += 2; + belowRest += 2; + break; + case Duration.Half: + aboveRest -= 2; + belowRest -= 2; + break; + case Duration.Quarter: + aboveRest -= 4; + belowRest += 2; + break; + case Duration.Eighth: + aboveRest -= 2; + belowRest += 2; + break; + case Duration.Sixteenth: + aboveRest -= 2; + belowRest += 4; + break; + case Duration.ThirtySecond: + aboveRest -= 4; + belowRest += 4; + break; + case Duration.SixtyFourth: + aboveRest -= 4; + belowRest += 6; + break; + case Duration.OneHundredTwentyEighth: + aboveRest -= 6; + belowRest += 6; + break; + case Duration.TwoHundredFiftySixth: + aboveRest -= 6; + belowRest += 8; + break; + } + if (this.minRestLine === null || this.minRestLine > aboveRest) { + this.minRestLine = aboveRest; + this.beatOfMinRestLine = beat; + } + if (this.maxRestLine === null || this.maxRestLine < belowRest) { + this.maxRestLine = belowRest; + this.beatOfMaxRestLine = beat; + } + } + private invert(direction: BeamDirection): BeamDirection { if (!this.invertBeamDirection) { return direction; @@ -207,8 +302,6 @@ export class BeamingHelper { if (fingeringCount > this.fingeringCount) { this.fingeringCount = fingeringCount; } - this._lastBeatLowestNote = null; - this._lastBeatHighestNote = null; this.checkNote(beat.minNote); this.checkNote(beat.maxNote); if (this.shortestDuration < beat.duration) { @@ -217,6 +310,9 @@ export class BeamingHelper { if (beat.hasTuplet) { this.hasTuplet = true; } + if (!beat.isRest) { + this._lastNonRestBeat = beat; + } } return add; } @@ -247,23 +343,19 @@ export class BeamingHelper { } if (this.beats.length === 1 && this.beats[0] === note.beat) { - if (!this._firstBeatLowestNote || lowestValueForNote < this._firstBeatLowestNoteCompareValue) { - this._firstBeatLowestNote = note; + if (this._firstBeatLowestNoteCompareValue === -1 || lowestValueForNote < this._firstBeatLowestNoteCompareValue) { this._firstBeatLowestNoteCompareValue = lowestValueForNote; } - if (!this._firstBeatHighestNote || highestValueForNote > this._firstBeatHighestNoteCompareValue) { - this._firstBeatHighestNote = note; + if (this._firstBeatHighestNoteCompareValue === -1 || highestValueForNote > this._firstBeatHighestNoteCompareValue) { this._firstBeatHighestNoteCompareValue = highestValueForNote; } } - if (!this._lastBeatLowestNote || lowestValueForNote < this._lastBeatLowestNoteCompareValue) { - this._lastBeatLowestNote = note; + if (this._lastBeatLowestNoteCompareValue === -1 || lowestValueForNote < this._lastBeatLowestNoteCompareValue) { this._lastBeatLowestNoteCompareValue = lowestValueForNote; } - if (!this._lastBeatHighestNote || highestValueForNote > this._lastBeatHighestNoteCompareValue) { - this._lastBeatHighestNote = note; + if (this._lastBeatHighestNoteCompareValue === -1 || highestValueForNote > this._lastBeatHighestNoteCompareValue) { this._lastBeatHighestNoteCompareValue = highestValueForNote; } @@ -277,7 +369,7 @@ export class BeamingHelper { } } - + // TODO: Check if this beaming is really correct, I'm not sure if we are connecting beats correctly private static canJoin(b1: Beat, b2: Beat): boolean { // is this a voice we can join with? @@ -373,4 +465,6 @@ export class BeamingHelper { !this._beatLineXPositions.get(beat.index)!.staffId ); } + + public drawingInfos: Map = new Map(); } diff --git a/test-data/visual-tests/music-notation/beams-advanced.gp b/test-data/visual-tests/music-notation/beams-advanced.gp new file mode 100644 index 000000000..655717838 Binary files /dev/null and b/test-data/visual-tests/music-notation/beams-advanced.gp differ diff --git a/test-data/visual-tests/music-notation/beams-advanced.png b/test-data/visual-tests/music-notation/beams-advanced.png new file mode 100644 index 000000000..674e0fb8b Binary files /dev/null and b/test-data/visual-tests/music-notation/beams-advanced.png differ diff --git a/test-data/visual-tests/music-notation/key-signatures.png b/test-data/visual-tests/music-notation/key-signatures.png index 2b1b09533..f4cd92e63 100644 Binary files a/test-data/visual-tests/music-notation/key-signatures.png and b/test-data/visual-tests/music-notation/key-signatures.png differ diff --git a/test-data/visual-tests/music-notation/notes-rests-beams.png b/test-data/visual-tests/music-notation/notes-rests-beams.png index 2b9c7a593..96546fcb0 100644 Binary files a/test-data/visual-tests/music-notation/notes-rests-beams.png and b/test-data/visual-tests/music-notation/notes-rests-beams.png differ diff --git a/test-data/visual-tests/notation-legend/let-ring-default.png b/test-data/visual-tests/notation-legend/let-ring-default.png index b63604a61..5d9159d77 100644 Binary files a/test-data/visual-tests/notation-legend/let-ring-default.png and b/test-data/visual-tests/notation-legend/let-ring-default.png differ diff --git a/test-data/visual-tests/notation-legend/let-ring-songbook.png b/test-data/visual-tests/notation-legend/let-ring-songbook.png index e3088522f..853536238 100644 Binary files a/test-data/visual-tests/notation-legend/let-ring-songbook.png and b/test-data/visual-tests/notation-legend/let-ring-songbook.png differ diff --git a/test-data/visual-tests/notation-legend/tap-riff-default.png b/test-data/visual-tests/notation-legend/tap-riff-default.png index 30a9c3c1c..4a9903eed 100644 Binary files a/test-data/visual-tests/notation-legend/tap-riff-default.png and b/test-data/visual-tests/notation-legend/tap-riff-default.png differ diff --git a/test-data/visual-tests/notation-legend/tap-riff-songbook.png b/test-data/visual-tests/notation-legend/tap-riff-songbook.png index 30a9c3c1c..4a9903eed 100644 Binary files a/test-data/visual-tests/notation-legend/tap-riff-songbook.png and b/test-data/visual-tests/notation-legend/tap-riff-songbook.png differ diff --git a/test-data/visual-tests/special-tracks/grand-staff.png b/test-data/visual-tests/special-tracks/grand-staff.png index 4bd76fe5c..a348c2d23 100644 Binary files a/test-data/visual-tests/special-tracks/grand-staff.png and b/test-data/visual-tests/special-tracks/grand-staff.png differ diff --git a/test/visualTests/features/MusicNotation.test.ts b/test/visualTests/features/MusicNotation.test.ts index 21feebd59..3dab3bc7c 100644 --- a/test/visualTests/features/MusicNotation.test.ts +++ b/test/visualTests/features/MusicNotation.test.ts @@ -48,4 +48,10 @@ describe('MusicNotationTests', () => { settings.display.staveProfile = StaveProfile.Score; await VisualTestHelper.runVisualTest('music-notation/forced-accidentals.gp', settings, [0, 1]); }); + + it('beams-advanced', async () => { + let settings: Settings = new Settings(); + settings.display.barsPerRow = 4; + await VisualTestHelper.runVisualTest('music-notation/beams-advanced.gp', settings); + }); });