Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src.compiler/csharp/CSharpAstTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
44 changes: 41 additions & 3 deletions src.csharp/AlphaTab/Core/EcmaScript/RegExp.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
15 changes: 12 additions & 3 deletions src.csharp/AlphaTab/Core/TypeHelper.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -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)
{
Expand Down
16 changes: 12 additions & 4 deletions src/platform/svg/SvgCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ export abstract class SvgCanvas implements ICanvas {
public settings!: Settings;

public beginRender(width: number, height: number): void {
this.buffer = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${width | 0}px" height="${
height | 0
}px" class="at-surface-svg">\n`;
this.buffer = `<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="${width | 0}px" height="${height | 0
}px" class="at-surface-svg">\n`;
this._currentPath = '';
this._currentPathIsEmpty = true;
}
Expand Down Expand Up @@ -137,10 +136,19 @@ export abstract class SvgCanvas implements ICanvas {
if (this.textAlign !== TextAlign.Left) {
s += ` text-anchor="${this.getSvgTextAlignment(this.textAlign)}"`;
}
s += `>${text}</text>`;
s += `>${SvgCanvas.escapeText(text)}</text>`;
this.buffer += s;
}

private static escapeText(text: string) {
return text
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}

protected getSvgTextAlignment(textAlign: TextAlign): string {
switch (textAlign) {
case TextAlign.Left:
Expand Down
151 changes: 92 additions & 59 deletions src/rendering/ScoreBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}


Expand Down
8 changes: 6 additions & 2 deletions src/rendering/glyphs/ScoreBeatGlyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
//
Expand Down
35 changes: 19 additions & 16 deletions src/rendering/utils/AccidentalHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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
Expand Down
Loading