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
32 changes: 32 additions & 0 deletions src.csharp/AlphaTab/Core/EcmaScript/Set.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace AlphaTab.Core.EcmaScript
{
public class Set<T>
{
private readonly HashSet<T> _data = new HashSet<T>();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item)
{
_data.Add(item);
}


[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Has(T item)
{
return _data.Contains(item);
}

public void ForEach(Action<T> action)
{
foreach (var i in _data)
{
action(i);
}
}
}
}
4 changes: 2 additions & 2 deletions src/AlphaTabApiBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -969,8 +969,8 @@ export class AlphaTabApiBase<TSettings> {
if (this.settings.player.enableUserInteraction) {
// for the selection ensure start < end
if (this._selectionEnd) {
let startTick: number = this._selectionStart!.beat.absoluteDisplayStart;
let endTick: number = this._selectionStart!.beat.absoluteDisplayStart;
let startTick: number = this._selectionStart!.beat.absolutePlaybackStart;
let endTick: number = this._selectionStart!.beat.absolutePlaybackStart;
if (endTick < startTick) {
let t: SelectionInfo = this._selectionStart!;
this._selectionStart = this._selectionEnd;
Expand Down
47 changes: 40 additions & 7 deletions src/model/Beat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Settings } from '@src/Settings';
import { Logger } from '@src/Logger';
import { BeamDirection } from '@src/rendering/utils/BeamDirection';
import { BeatCloner } from '@src/generated/model/BeatCloner';
import { GraceGroup } from './GraceGroup';

/**
* Lists the different modes on how beaming for a beat should be done.
Expand Down Expand Up @@ -324,6 +325,22 @@ export class Beat {
*/
public graceType: GraceType = GraceType.None;

/**
* Gets or sets the grace group this beat belongs to.
* If this beat is not a grace note, it holds the group which belongs to this beat.
* @json_ignore
* @clone_ignore
*/
public graceGroup: GraceGroup | null = null;

/**
* Gets or sets the index of this beat within the grace group if
* this is a grace beat.
* @json_ignore
* @clone_ignore
*/
public graceIndex: number = -1;

/**
* Gets or sets the pickstroke applied on this beat.
*/
Expand Down Expand Up @@ -507,7 +524,7 @@ export class Beat {
public updateDurations(): void {
let ticks: number = this.calculateDuration();
this.playbackDuration = ticks;
this.displayDuration = ticks;

switch (this.graceType) {
case GraceType.BeforeBeat:
case GraceType.OnBeat:
Expand All @@ -522,20 +539,17 @@ export class Beat {
this.playbackDuration = MidiUtils.toTicks(Duration.ThirtySecond);
break;
}
this.displayDuration = 0;
break;
case GraceType.BendGrace:
this.playbackDuration /= 2;
this.displayDuration = 0;
break;
default:
this.displayDuration = ticks;
let previous: Beat | null = this.previousBeat;
if (previous && previous.graceType === GraceType.BendGrace) {
this.playbackDuration = previous.playbackDuration;
} else {
while (previous && previous.graceType === GraceType.OnBeat) {
// if the previous beat is a on-beat grace it steals the duration from this beat
this.playbackDuration -= previous.playbackDuration;
previous = previous.previousBeat;
}
}
break;
}
Expand All @@ -562,6 +576,22 @@ export class Beat {
this.automations.push(Automation.buildInstrumentAutomation(false, 0, this.voice.bar.staff.track.playbackInfo.program));
}

switch (this.graceType) {
case GraceType.OnBeat:
case GraceType.BeforeBeat:
let numberOfGraceBeats: number = this.graceGroup!.beats.length;
// set right duration for beaming/display
if (numberOfGraceBeats === 1) {
this.duration = Duration.Eighth;
} else if (numberOfGraceBeats === 2) {
this.duration = Duration.Sixteenth;
} else {
this.duration = Duration.ThirtySecond;
}
break;
}


let displayMode: NotationMode = !settings ? NotationMode.GuitarPro : settings.notation.notationMode;
let isGradual: boolean = this.text === 'grad' || this.text === 'grad.';
if (isGradual && displayMode === NotationMode.SongBook) {
Expand Down Expand Up @@ -759,6 +789,9 @@ export class Beat {
cloneNote.isTieDestination = true;
}
this.graceType = GraceType.BendGrace;
this.graceGroup = new GraceGroup();
this.graceGroup.addBeat(this);
this.graceGroup.isComplete = true;
this.updateDurations();
this.voice.insertBeat(this, cloneBeat);
}
Expand Down
35 changes: 35 additions & 0 deletions src/model/GraceGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Beat } from './Beat';

/**
* Represents a group of grace beats that belong together
*/
export class GraceGroup {
/**
* All beats within this group.
*/
public beats: Beat[] = [];

/**
* Gets a unique ID for this grace group.
*/
public id: string = 'empty';

/**
* true if the grace beat are followed by a normal beat within the same
* bar.
*/
public isComplete: boolean = false;

/**
* Adds a new beat to this group
* @param beat The beat to add
*/
public addBeat(beat: Beat) {
if (this.beats.length === 0) {
this.id = beat.absoluteDisplayStart + "_" + beat.voice.index;
}
beat.graceIndex = this.beats.length;
beat.graceGroup = this;
this.beats.push(beat);
}
}
138 changes: 77 additions & 61 deletions src/model/Voice.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { MidiUtils } from '@src/midi/MidiUtils';
import { Bar } from '@src/model/Bar';
import { Beat } from '@src/model/Beat';
import { Duration } from '@src/model/Duration';
import { GraceType } from '@src/model/GraceType';
import { Settings } from '@src/Settings';
import { GraceGroup } from './GraceGroup';

/**
* A voice represents a group of beats
Expand Down Expand Up @@ -98,90 +97,107 @@ export class Voice {
this.isEmpty = false;
}

public getBeatAtDisplayStart(displayStart: number): Beat | null {
if (this._beatLookup.has(displayStart)) {
return this._beatLookup.get(displayStart)!;
public getBeatAtPlaybackStart(playbackStart: number): Beat | null {
if (this._beatLookup.has(playbackStart)) {
return this._beatLookup.get(playbackStart)!;
}
return null;
}

public finish(settings: Settings): void {
this._beatLookup = new Map<number, Beat>();
let currentGraceGroup: GraceGroup | null = null;
for (let index: number = 0; index < this.beats.length; index++) {
let beat: Beat = this.beats[index];
beat.index = index;
this.chain(beat);
if (beat.graceType === GraceType.None) {
beat.graceGroup = currentGraceGroup;
if (currentGraceGroup) {
currentGraceGroup.isComplete = true;
}
currentGraceGroup = null;
} else {
if (!currentGraceGroup) {
currentGraceGroup = new GraceGroup();
}
currentGraceGroup.addBeat(beat);
}
}

let currentDisplayTick: number = 0;
let currentPlaybackTick: number = 0;
for (let i: number = 0; i < this.beats.length; i++) {
let beat: Beat = this.beats[i];
beat.index = i;
beat.finish(settings);
if (beat.graceType === GraceType.None || beat.graceType === GraceType.BendGrace) {
beat.displayStart = currentDisplayTick;
beat.playbackStart = currentPlaybackTick;
currentDisplayTick += beat.displayDuration;
currentPlaybackTick += beat.playbackDuration;
} else {
if (!beat.previousBeat || beat.previousBeat.graceType === GraceType.None) {
// find note which is not a grace note
let nonGrace: Beat | null = beat;
let numberOfGraceBeats: number = 0;
while (nonGrace && nonGrace.graceType !== GraceType.None) {
nonGrace = nonGrace.nextBeat;
numberOfGraceBeats++;
}
let graceDuration: Duration = Duration.Eighth;
let stolenDuration: number = 0;
if (numberOfGraceBeats === 1) {
graceDuration = Duration.Eighth;
} else if (numberOfGraceBeats === 2) {
graceDuration = Duration.Sixteenth;
} else {
graceDuration = Duration.ThirtySecond;
}
if (nonGrace) {
nonGrace.updateDurations();
}
// grace beats have 1/4 size of the non grace beat preceeding them
let perGraceDisplayDuration: number = !beat.previousBeat
? MidiUtils.toTicks(Duration.ThirtySecond)
: (((beat.previousBeat.displayDuration / 4) | 0) / numberOfGraceBeats) | 0;
// move all grace beats
let graceBeat: Beat | null = this.beats[i];
for (let j: number = 0; j < numberOfGraceBeats && graceBeat; j++) {
graceBeat.duration = graceDuration;
graceBeat.updateDurations();
graceBeat.displayStart =
currentDisplayTick - (numberOfGraceBeats - j + 1) * perGraceDisplayDuration;
graceBeat.displayDuration = perGraceDisplayDuration;
stolenDuration += graceBeat.playbackDuration;
graceBeat = graceBeat.nextBeat;
}
// steal needed duration from beat duration
if (beat.graceType === GraceType.BeforeBeat) {
if (beat.previousBeat) {
beat.previousBeat.playbackDuration -= stolenDuration;

// if this beat is a non-grace but has grace notes
// we need to first steal the duration from the right beat
// and place the grace beats correctly
if (beat.graceType === GraceType.None) {
if (beat.graceGroup) {
const firstGraceBeat = beat.graceGroup!.beats[0];
const lastGraceBeat = beat.graceGroup!.beats[beat.graceGroup!.beats.length - 1];
if (firstGraceBeat.graceType !== GraceType.BendGrace) {
// find out the stolen duration first
let stolenDuration: number = (lastGraceBeat.playbackStart + lastGraceBeat.playbackDuration) - firstGraceBeat.playbackStart;

switch (firstGraceBeat.graceType) {
case GraceType.BeforeBeat:
// steal duration from previous beat and then place grace beats newly
if (firstGraceBeat.previousBeat) {
firstGraceBeat.previousBeat.playbackDuration -= stolenDuration;
// place beats starting after new beat end
if (firstGraceBeat.previousBeat.voice == this) {
currentPlaybackTick = firstGraceBeat.previousBeat.playbackStart +
firstGraceBeat.previousBeat.playbackDuration;
} else {
// stealing into the previous bar
currentPlaybackTick = -stolenDuration;
}
} else {
// before-beat on start is somehow not possible as it causes negative ticks
currentPlaybackTick = -stolenDuration;
}

for (const graceBeat of beat.graceGroup!.beats) {
this._beatLookup.delete(graceBeat.playbackStart);
graceBeat.playbackStart = currentPlaybackTick;
this._beatLookup.set(graceBeat.playbackStart, beat);
currentPlaybackTick += graceBeat.playbackDuration;
}

break;
case GraceType.OnBeat:
// steal duration from current beat
beat.playbackDuration -= stolenDuration;
if (lastGraceBeat.voice === this) {
// with changed durations, update current position to be after the last grace beat
currentPlaybackTick = lastGraceBeat.playbackStart + lastGraceBeat.playbackDuration;
} else {
// if last grace beat is on the previous bar, we shift the time back to have the note played earlier
currentPlaybackTick = -stolenDuration;
}
break;
}
currentPlaybackTick -= stolenDuration;
} else if (nonGrace && beat.graceType === GraceType.OnBeat) {
nonGrace.playbackDuration -= stolenDuration;
}
}
beat.playbackStart = currentPlaybackTick;
currentPlaybackTick = beat.playbackStart + beat.playbackDuration;
}

if (beat.fermata) {
this.bar.masterBar.addFermata(beat.playbackStart, beat.fermata);
} else {
beat.fermata = this.bar.masterBar.getFermata(beat);
}

if(beat.fermata) {
this.bar.masterBar.addFermata(beat.playbackStart, beat.fermata);
} else {
beat.fermata = this.bar.masterBar.getFermata(beat);
this._beatLookup.set(beat.playbackStart, beat);
}

beat.displayStart = currentDisplayTick;
beat.playbackStart = currentPlaybackTick;
beat.finishTuplet();
this._beatLookup.set(beat.displayStart, beat);
currentDisplayTick += beat.displayDuration;
currentPlaybackTick += beat.playbackDuration;
}
}

Expand Down
1 change: 0 additions & 1 deletion src/rendering/ScoreBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,6 @@ export class ScoreBarRenderer extends BarRendererBase {
}
}

// TODO[performance]: Maybe we should cache this (check profiler)
public getNoteLine(n: Note): number {
return this.accidentalHelper.getNoteLine(n);
}
Expand Down
2 changes: 1 addition & 1 deletion src/rendering/effects/DynamicsEffectInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class DynamicsEffectInfo extends EffectBarRendererInfo {
if (show && beat.voice.index > 0) {
for (let voice of beat.voice.bar.voices) {
if (voice.index < beat.voice.index) {
let beatAtSamePos = voice.getBeatAtDisplayStart(beat.displayStart);
let beatAtSamePos = voice.getBeatAtPlaybackStart(beat.playbackStart);
if (
beatAtSamePos &&
beat.dynamics === beatAtSamePos.dynamics &&
Expand Down
Loading