Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add beat slash reading and rendering #1646

Merged
merged 5 commits into from
Aug 25, 2024
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
5 changes: 5 additions & 0 deletions src.csharp/AlphaTab/Core/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public static T Find<T>(this IList<T> list, Func<T, bool> predicate)
return list.FirstOrDefault(predicate);
}

public static bool Some<T>(this IList<T> list, Func<T, bool> predicate)
{
return list.Any(predicate);
}

public static IList<T> Splice<T>(this IList<T> data, double start, double deleteCount)
{
var items = data.GetRange((int) start, (int) deleteCount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public class List<T> : Iterable<T> {
return List(_data.filter(predicate))
}

public fun some(predicate: (T) -> Boolean): Boolean {
return _data.any(predicate)
}

public fun indexOf(value: T): Double {
return _data.indexOf(value).toDouble()
}
Expand Down
3 changes: 3 additions & 0 deletions src/exporter/GpifWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,9 @@ export class GpifWriter {
}

beatNode.addElement('ConcertPitchStemOrientation').innerText = 'Undefined';
if(beat.slashed) {
beatNode.addElement('Slashed');
}
if (!beat.isRest) {
beatNode.addElement('Notes').innerText = beat.notes.map(n => n.id).join(' ');
}
Expand Down
1 change: 1 addition & 0 deletions src/generated/model/BeatCloner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class BeatCloner {
clone.slap = original.slap;
clone.tap = original.tap;
clone.text = original.text;
clone.slashed = original.slashed;
clone.brushType = original.brushType;
clone.brushDuration = original.brushDuration;
clone.tupletDenominator = original.tupletDenominator;
Expand Down
4 changes: 4 additions & 0 deletions src/generated/model/BeatSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class BeatSerializer {
o.set("slap", obj.slap);
o.set("tap", obj.tap);
o.set("text", obj.text);
o.set("slashed", obj.slashed);
o.set("brushtype", obj.brushType as number);
o.set("brushduration", obj.brushDuration);
o.set("tupletdenominator", obj.tupletDenominator);
Expand Down Expand Up @@ -136,6 +137,9 @@ export class BeatSerializer {
case "text":
obj.text = v as string | null;
return true;
case "slashed":
obj.slashed = v! as boolean;
return true;
case "brushtype":
obj.brushType = JsonHelper.parseEnum<BrushType>(v, BrushType)!;
return true;
Expand Down
4 changes: 4 additions & 0 deletions src/importer/AlphaTexImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1536,6 +1536,10 @@ export class AlphaTexImporter extends ScoreImporter {
this._sy = this.newSy();
}
return true;
} else if (syData === 'slashed') {
beat.slashed = true;
this._sy = this.newSy();
return true;
} else {
// string didn't match any beat effect syntax
return false;
Expand Down
3 changes: 3 additions & 0 deletions src/importer/GpifParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,9 @@ export class GpifParser {
beat.lyrics = this.parseBeatLyrics(c);
this._skipApplyLyrics = true;
break;
case 'Slashed':
beat.slashed = true;
break;
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/model/Beat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ export class Beat {
*/
public text: string | null = null;

/**
* Gets or sets whether this beat should be rendered as slashed note.
*/
public slashed: boolean = false;

/**
* Gets or sets the brush type applied to the notes of this beat.
*/
Expand Down
20 changes: 12 additions & 8 deletions src/rendering/LineBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,10 @@ export abstract class LineBarRenderer extends BarRendererBase {
canvas.moveTo(cx + this.x + startX, (cy + this.y + startY - offset) | 0);
if (bracketsAsArcs) {
canvas.quadraticCurveTo(
cx + this.x + (offset1X + startX) / 2, (cy + this.y + offset1Y - offset - size) | 0,
cx + this.x + offset1X, (cy + this.y + offset1Y - offset - size) | 0
cx + this.x + (offset1X + startX) / 2,
(cy + this.y + offset1Y - offset - size) | 0,
cx + this.x + offset1X,
(cy + this.y + offset1Y - offset - size) | 0
);
} else {
canvas.lineTo(cx + this.x + startX, (cy + this.y + startY - offset - size) | 0);
Expand All @@ -324,8 +326,10 @@ export abstract class LineBarRenderer extends BarRendererBase {
canvas.moveTo(cx + this.x + offset2X, (cy + this.y + offset2Y - offset - size) | 0);
if (bracketsAsArcs) {
canvas.quadraticCurveTo(
cx + this.x + (endX + offset2X) / 2, (cy + this.y + offset2Y - offset - size) | 0,
cx + this.x + endX, (cy + this.y + endY - offset) | 0,
cx + this.x + (endX + offset2X) / 2,
(cy + this.y + offset2Y - offset - size) | 0,
cx + this.x + endX,
(cy + this.y + endY - offset) | 0
);
} else {
canvas.lineTo(cx + this.x + endX, (cy + this.y + endY - offset - size) | 0);
Expand Down Expand Up @@ -367,8 +371,8 @@ export abstract class LineBarRenderer extends BarRendererBase {
}
}

protected abstract getFlagTopY(beat: Beat): number;
protected abstract getFlagBottomY(beat: Beat): number;
protected abstract getFlagTopY(beat: Beat, direction: BeamDirection): number;
protected abstract getFlagBottomY(beat: Beat, direction: BeamDirection): number;
protected shouldPaintFlag(beat: Beat, h: BeamingHelper): boolean {
// no flags for bend grace beats
if (beat.graceType === GraceType.BendGrace) {
Expand Down Expand Up @@ -411,8 +415,8 @@ export abstract class LineBarRenderer extends BarRendererBase {
let stemSize: number = this.getFlagStemSize(h.shortestDuration);
let beatLineX: number = h.getBeatLineX(beat);
let direction: BeamDirection = this.getBeamDirection(h);
let topY: number = this.getFlagTopY(beat);
let bottomY: number = this.getFlagBottomY(beat);
let topY: number = this.getFlagTopY(beat, direction);
let bottomY: number = this.getFlagBottomY(beat, direction);
let beamY: number = 0;
if (direction === BeamDirection.Down) {
bottomY += stemSize * scaleMod;
Expand Down
4 changes: 2 additions & 2 deletions src/rendering/NumberedBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,11 @@ export class NumberedBarRenderer extends LineBarRenderer {
return super.tupletOffset + this.resources.numberedNotationFont.size * this.scale;
}

protected override getFlagTopY(_beat: Beat): number {
protected override getFlagTopY(_beat: Beat, _direction:BeamDirection): number {
return this.getLineY(0) - (SlashNoteHeadGlyph.NoteHeadHeight / 2) * this.scale;
}

protected override getFlagBottomY(_beat: Beat): number {
protected override getFlagBottomY(_beat: Beat, _direction:BeamDirection): number {
return this.getLineY(0) - (SlashNoteHeadGlyph.NoteHeadHeight / 2) * this.scale;
}

Expand Down
73 changes: 66 additions & 7 deletions src/rendering/ScoreBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,41 @@ export class ScoreBarRenderer extends LineBarRenderer {
this.paintTuplets(cx, cy, canvas);
}

protected override getFlagTopY(beat: Beat): number {
private getSlashFlagY(duration: Duration, direction: BeamDirection) {
let line = (this.heightLineCount - 1) / 2;
let offset = 0;
switch (duration) {
case Duration.QuadrupleWhole:
case Duration.DoubleWhole:
case Duration.Whole:
offset += 2;
break;
default:
offset += 1;
break;
}

if (direction == BeamDirection.Down) {
line += offset;
} else {
line -= offset;
}

const slashY = this.getLineY(line);
return slashY;
}

protected override getFlagTopY(beat: Beat, direction: BeamDirection): number {
if (beat.slashed) {
return this.getSlashFlagY(beat.duration, direction);
}
return this.getScoreY(this.accidentalHelper.getMinLine(beat));
}

protected override getFlagBottomY(beat: Beat): number {
protected override getFlagBottomY(beat: Beat, direction: BeamDirection): number {
if (beat.slashed) {
return this.getSlashFlagY(beat.duration, direction);
}
return this.getScoreY(this.accidentalHelper.getMaxLine(beat));
}

Expand Down Expand Up @@ -200,6 +230,11 @@ export class ScoreBarRenderer extends LineBarRenderer {
}

public override getNoteY(note: Note, requestedPosition: NoteYPosition): number {
if(note.beat.slashed) {
let line = (this.heightLineCount - 1) / 2;
return this.getLineY(line);
}

let y = super.getNoteY(note, requestedPosition);
if (isNaN(y)) {
// NOTE: some might request the note position before the glyphs have been created
Expand Down Expand Up @@ -244,7 +279,7 @@ export class ScoreBarRenderer extends LineBarRenderer {
const firstBeat = h.beats[0];
const lastBeat = h.beats[h.beats.length - 1];

let isRest = h.isRestBeamHelper;
const isRest = h.isRestBeamHelper;

// 1. put direct diagonal line.
drawingInfo.startBeat = firstBeat;
Expand All @@ -255,8 +290,8 @@ export class ScoreBarRenderer extends LineBarRenderer {
} else {
drawingInfo.startY =
direction === BeamDirection.Up
? this.getScoreY(this.accidentalHelper.getMinLine(firstBeat)) - stemSize
: this.getScoreY(this.accidentalHelper.getMaxLine(firstBeat)) + stemSize;
? this.getFlagTopY(firstBeat, direction) - stemSize
: this.getFlagBottomY(firstBeat, direction) + stemSize;
}

drawingInfo.endBeat = lastBeat;
Expand All @@ -267,8 +302,8 @@ export class ScoreBarRenderer extends LineBarRenderer {
} else {
drawingInfo.endY =
direction === BeamDirection.Up
? this.getScoreY(this.accidentalHelper.getMinLine(lastBeat)) - stemSize
: this.getScoreY(this.accidentalHelper.getMaxLine(lastBeat)) + stemSize;
? this.getFlagTopY(lastBeat, direction) - stemSize
: this.getFlagBottomY(lastBeat, direction) + stemSize;
}
// 2. ensure max height
// we use the min/max notes to place the beam along their real position
Expand Down Expand Up @@ -359,13 +394,37 @@ export class ScoreBarRenderer extends LineBarRenderer {
}
}
}

// check if slash shifts bar up or down
if (h.slashBeats.length > 0) {
for (const b of h.slashBeats) {
const yGivenByCurrentValues = drawingInfo.calcY(h.getBeatLineX(b));
let yNeededForSlash = this.getSlashFlagY(b.duration, direction);

if (direction === BeamDirection.Up) {
yNeededForSlash -= stemSize;
} else if (direction === BeamDirection.Down) {
yNeededForSlash += stemSize;
}

const diff = yNeededForSlash - yGivenByCurrentValues;
if (diff > 0) {
drawingInfo.startY += diff;
drawingInfo.endY += diff;
}
}
}
}
}

return h.drawingInfos.get(direction)!.calcY(x);
}

protected override getBarLineStart(beat: Beat, direction: BeamDirection): number {
if (beat.slashed) {
return this.getSlashFlagY(beat.duration, direction);
}

return direction === BeamDirection.Up
? this.getScoreY(this.accidentalHelper.getMaxLine(beat))
: this.getScoreY(this.accidentalHelper.getMinLine(beat));
Expand Down
5 changes: 3 additions & 2 deletions src/rendering/SlashBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class SlashBarRenderer extends LineBarRenderer {
super(renderer, bar);
// ignore numbered notation here
this._isOnlySlash = !bar.staff.showTablature && !bar.staff.showStandardNotation;
this.helpers.preferredBeamDirection = BeamDirection.Up;
}

public override get lineSpacing(): number {
Expand Down Expand Up @@ -74,11 +75,11 @@ export class SlashBarRenderer extends LineBarRenderer {
return 0;
}

protected override getFlagTopY(_beat: Beat): number {
protected override getFlagTopY(_beat: Beat, _direction:BeamDirection): number {
return this.getLineY(0) - (SlashNoteHeadGlyph.NoteHeadHeight / 2) * this.scale;
}

protected override getFlagBottomY(_beat: Beat): number {
protected override getFlagBottomY(_beat: Beat, _direction:BeamDirection): number {
return this.getLineY(0) - (SlashNoteHeadGlyph.NoteHeadHeight / 2) * this.scale;
}

Expand Down
4 changes: 2 additions & 2 deletions src/rendering/TabBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export class TabBarRenderer extends LineBarRenderer {
return super.drawBeamHelperAsFlags(h) || this.settings.notation.rhythmMode === TabRhythmMode.ShowWithBeams;
}

protected override getFlagTopY(beat: Beat): number {
protected override getFlagTopY(beat: Beat, _direction:BeamDirection): number {
const startGlyph: TabBeatGlyph = this.getOnNotesGlyphForBeat(beat) as TabBeatGlyph;
if (!startGlyph.noteNumbers || beat.duration === Duration.Half) {
return this.height - this.settings.notation.rhythmHeight * this.settings.display.scale - this.tupletSize;
Expand All @@ -186,7 +186,7 @@ export class TabBarRenderer extends LineBarRenderer {
}
}

protected override getFlagBottomY(_beat: Beat): number {
protected override getFlagBottomY(_beat: Beat, _direction:BeamDirection): number {
return this.getFlagAndBarPos();
}

Expand Down
Loading