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: 8 additions & 2 deletions src.csharp/AlphaTab/Core/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using AlphaTab.Core.EcmaScript;

namespace AlphaTab.Core
{
Expand Down Expand Up @@ -204,7 +203,7 @@ public static void Sort<T>(this IList<T> data)
{
switch (data)
{
case System.Collections.Generic.List<T> l:
case List<T> l:
l.Sort();
break;
case T[] array:
Expand Down Expand Up @@ -252,6 +251,13 @@ public static string Substr(this string s, double start, double length)
return s.Substring((int) start, (int) length);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string PadStart(this string s, double length, string pad)
{
// NOTE: we only need single char padding for now
return s.PadLeft((int)length, pad[0]);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Substr(this string s, double start)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ public static uint RgbaToColor(double r, double g, double b, double a)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FillRect(double x, double y, double w, double h)
{
_canvas.FillRect((float)x, (int)y, (float)w, (float)h);
_canvas.FillRect((float)x, (float)y, (float)w, (float)h);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void StrokeRect(double x, double y, double w, double h)
{
_canvas.StrokeRect((float)x, (int)y, (float)w, (float)h);
_canvas.StrokeRect((float)x, (float)y, (float)w, (float)h);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,10 @@ internal fun String.repeat(count:Double): String {
return this.repeat(count.toInt())
}

internal fun String.padStart(length:Double, pad:String): String {
return this.padStart(length.toInt(), pad[0])
}

internal val Throwable.stack: String
get() = this.stackTraceToString()

Expand Down
30 changes: 20 additions & 10 deletions src/AlphaTabApiBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ export class AlphaTabApiBase<TSettings> {
if (this.settings.player.enablePlayer) {
this.setupPlayer();
if (score) {
this.player?.applyTranspositionPitches(MidiFileGenerator.buildTranspositionPitches(score, this.settings));
this.player?.applyTranspositionPitches(
MidiFileGenerator.buildTranspositionPitches(score, this.settings)
);
}
} else {
this.destroyPlayer();
Expand Down Expand Up @@ -290,7 +292,7 @@ export class AlphaTabApiBase<TSettings> {
for (let track of tracks) {
this._trackIndexes.push(track.index);
}
this._trackIndexLookup = new Set<number>(this._trackIndexes)
this._trackIndexLookup = new Set<number>(this._trackIndexes);
this.onScoreLoaded(score);
this.loadMidiForScore();
this.render();
Expand All @@ -300,7 +302,7 @@ export class AlphaTabApiBase<TSettings> {
for (let track of tracks) {
this._trackIndexes.push(track.index);
}
this._trackIndexLookup = new Set<number>(this._trackIndexes)
this._trackIndexLookup = new Set<number>(this._trackIndexes);
this.render();
}
}
Expand Down Expand Up @@ -608,9 +610,10 @@ export class AlphaTabApiBase<TSettings> {
}

private loadMidiForScore(): void {
if (!this.player || !this.score || !this.player.isReady) {
if (!this.score) {
return;
}

Logger.debug('AlphaTab', 'Generating Midi');
let midiFile: MidiFile = new MidiFile();
let handler: AlphaSynthMidiFileHandler = new AlphaSynthMidiFileHandler(midiFile);
Expand All @@ -622,8 +625,12 @@ export class AlphaTabApiBase<TSettings> {
generator.generate();
this._tickCache = generator.tickLookup;
this.onMidiLoad(midiFile);
this.player.loadMidiFile(midiFile);
this.player.applyTranspositionPitches(generator.transpositionPitches);

const player = this.player;
if (player) {
player.loadMidiFile(midiFile);
player.applyTranspositionPitches(generator.transpositionPitches);
}
}

/**
Expand Down Expand Up @@ -1049,8 +1056,7 @@ export class AlphaTabApiBase<TSettings> {
let nextBeatBoundings: BeatBounds | null = cache.findBeat(nextBeat);
if (
nextBeatBoundings &&
nextBeatBoundings.barBounds.masterBarBounds.staffSystemBounds ===
barBoundings.staffSystemBounds
nextBeatBoundings.barBounds.masterBarBounds.staffSystemBounds === barBoundings.staffSystemBounds
) {
nextBeatX = nextBeatBoundings.visualBounds.x;
}
Expand Down Expand Up @@ -1174,8 +1180,12 @@ export class AlphaTabApiBase<TSettings> {
this.settings.player.enableUserInteraction
) {
if (this._selectionEnd) {
let startTick: number = this._tickCache?.getBeatStart(this._selectionStart!.beat) ?? this._selectionStart!.beat.absolutePlaybackStart;
let endTick: number = this._tickCache?.getBeatStart(this._selectionEnd!.beat) ?? this._selectionEnd!.beat.absolutePlaybackStart;
let startTick: number =
this._tickCache?.getBeatStart(this._selectionStart!.beat) ??
this._selectionStart!.beat.absolutePlaybackStart;
let endTick: number =
this._tickCache?.getBeatStart(this._selectionEnd!.beat) ??
this._selectionEnd!.beat.absolutePlaybackStart;
if (endTick < startTick) {
let t: SelectionInfo = this._selectionStart!;
this._selectionStart = this._selectionEnd;
Expand Down
2 changes: 2 additions & 0 deletions src/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { BeatBarreEffectInfo } from './rendering/effects/BeatBarreEffectInfo';
import { NoteOrnamentEffectInfo } from './rendering/effects/NoteOrnamentEffectInfo';
import { RasgueadoEffectInfo } from './rendering/effects/RasgueadoEffectInfo';
import { DirectionsEffectInfo } from './rendering/effects/DirectionsEffectInfo';
import { BeatTimerEffectInfo } from './rendering/effects/BeatTimerEffectInfo';

export class LayoutEngineFactory {
public readonly vertical: boolean;
Expand Down Expand Up @@ -506,6 +507,7 @@ export class Environment {
new AlternateEndingsEffectInfo(),
new FreeTimeEffectInfo(),
new TextEffectInfo(),
new BeatTimerEffectInfo(),
new ChordsEffectInfo()
]),
// no before-slash-hideable
Expand Down
5 changes: 5 additions & 0 deletions src/NotationSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,11 @@ export enum NotationElement {
* The directions indicators like coda and segno.
*/
EffectDirections,

/**
* The absolute playback time of beats.
*/
EffectBeatTimer,
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/RenderingResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export class RenderingResources {
*/
public effectFont: Font = new Font(RenderingResources.serifFont, 12, FontStyle.Italic);

/**
* Gets or sets the font to use for displaying beat time information in the music sheet.
*/
public timerFont: Font = new Font(RenderingResources.serifFont, 12, FontStyle.Plain);

/**
* Gets or sets the font to use for displaying the directions texts.
*/
Expand Down
4 changes: 4 additions & 0 deletions src/exporter/GpifWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,10 @@ export class GpifWriter {
beatNode.addElement('Wah').innerText = WahPedal[beat.wahPedal];
}

if(beat.showTimer) {
beatNode.addElement('Timer').innerText = (beat.timer ?? 0).toString();
}

this.writeBeatProperties(beatNode, beat);
this.writeBeatXProperties(beatNode, beat);

Expand Down
4 changes: 4 additions & 0 deletions src/generated/RenderingResourcesJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export interface RenderingResourcesJson {
* Gets or sets the font to use for displaying certain effect related elements in the music sheet.
*/
effectFont?: FontJson;
/**
* Gets or sets the font to use for displaying beat time information in the music sheet.
*/
timerFont?: FontJson;
/**
* Gets or sets the font to use for displaying the directions texts.
*/
Expand Down
4 changes: 4 additions & 0 deletions src/generated/RenderingResourcesSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class RenderingResourcesSerializer {
o.set("subtitlefont", Font.toJson(obj.subTitleFont));
o.set("wordsfont", Font.toJson(obj.wordsFont));
o.set("effectfont", Font.toJson(obj.effectFont));
o.set("timerfont", Font.toJson(obj.timerFont));
o.set("directionsfont", Font.toJson(obj.directionsFont));
o.set("fretboardnumberfont", Font.toJson(obj.fretboardNumberFont));
o.set("numberednotationfont", Font.toJson(obj.numberedNotationFont));
Expand Down Expand Up @@ -59,6 +60,9 @@ export class RenderingResourcesSerializer {
case "effectfont":
obj.effectFont = Font.fromJson(v)!;
return true;
case "timerfont":
obj.timerFont = Font.fromJson(v)!;
return true;
case "directionsfont":
obj.directionsFont = Font.fromJson(v)!;
return true;
Expand Down
2 changes: 2 additions & 0 deletions src/generated/model/BeatCloner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export class BeatCloner {
clone.barreFret = original.barreFret;
clone.barreShape = original.barreShape;
clone.rasgueado = original.rasgueado;
clone.showTimer = original.showTimer;
clone.timer = original.timer;
return clone;
}
}
8 changes: 8 additions & 0 deletions src/generated/model/BeatSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export class BeatSerializer {
o.set("barrefret", obj.barreFret);
o.set("barreshape", obj.barreShape as number);
o.set("rasgueado", obj.rasgueado as number);
o.set("showtimer", obj.showTimer);
o.set("timer", obj.timer);
return o;
}
public static setProperty(obj: Beat, property: string, v: unknown): boolean {
Expand Down Expand Up @@ -235,6 +237,12 @@ export class BeatSerializer {
case "rasgueado":
obj.rasgueado = JsonHelper.parseEnum<Rasgueado>(v, Rasgueado)!;
return true;
case "showtimer":
obj.showTimer = v! as boolean;
return true;
case "timer":
obj.timer = v as number | null;
return true;
}
return false;
}
Expand Down
4 changes: 4 additions & 0 deletions src/importer/AlphaTexImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2306,6 +2306,10 @@ export class AlphaTexImporter extends ScoreImporter {
}
this._sy = this.newSy();
return true;
} else if (syData === 'timer') {
beat.showTimer = true;
this._sy = this.newSy();
return true;
} else {
// string didn't match any beat effect syntax
return false;
Expand Down
6 changes: 6 additions & 0 deletions src/importer/GpifParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1741,6 +1741,12 @@ export class GpifParser {
break;
}
break;
case 'Timer':
beat.showTimer = true;
if(c.innerText.length > 0) {
beat.timer = parseInt(c.innerText);
}
break;
}
}
}
Expand Down
51 changes: 51 additions & 0 deletions src/midi/MidiFileGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export class MidiFileGenerator {
private _handler: IMidiFileHandler;
private _programsPerChannel: Map<number, number> = new Map<number, number>();

private _currentTime: number = 0;
private _calculatedBeatTimers: Set<number> = new Set<number>();

/**
* Gets a lookup object which can be used to quickly find beats and bars
* at a given midi tick position.
Expand Down Expand Up @@ -102,6 +105,8 @@ export class MidiFileGenerator {
*/
public generate(): void {
this.transpositionPitches.clear();
this._calculatedBeatTimers.clear();
this._currentTime = 0;

// initialize tracks
for (const track of this._score.tracks) {
Expand Down Expand Up @@ -264,9 +269,49 @@ export class MidiFileGenerator {
private generateBar(bar: Bar, barStartTick: number, tempoOnBarStart: number): void {
let playbackBar: Bar = this.getPlaybackBar(bar);

const barStartTime = this._currentTime;
for (const v of playbackBar.voices) {
this._currentTime = barStartTime;
this.generateVoice(v, barStartTick, bar, tempoOnBarStart);
}

// calculate the real bar end time (bars might be not full or overfilled)
const masterBar = playbackBar.masterBar;
const tickDuration = masterBar.calculateDuration();
const tempoAutomations = masterBar.tempoAutomations.slice();
if (tempoAutomations.length === 0) {
// fast path: no tempo automations -> simply apply whole duration
this._currentTime = barStartTime + MidiUtils.ticksToMillis(tickDuration, tempoOnBarStart);
} else {
// slow path: loop through slices and advance time
this._currentTime = barStartTime;

let currentTick = barStartTick;
let currentTempo = tempoOnBarStart;

const endTick = barStartTick + tickDuration;

for (const automation of tempoAutomations) {
// calculate the tick difference to the next tempo automation
const automationTick = tickDuration * automation.ratioPosition;
const diff = automationTick - currentTick;

// apply the time
if (diff > 0) {
this._currentTime += MidiUtils.ticksToMillis(diff, currentTempo);
}

// apply automation advance time
currentTempo = automation.value;
currentTick += diff;
}

// apply time until end
const remainingTick = endTick - currentTick;
if (remainingTick > 0) {
this._currentTime += MidiUtils.ticksToMillis(remainingTick, currentTempo);
}
}
}

private getPlaybackBar(bar: Bar): Bar {
Expand Down Expand Up @@ -336,6 +381,12 @@ export class MidiFileGenerator {
}
}

if (beat.showTimer && !this._calculatedBeatTimers.has(beat.id)) {
beat.timer = this._currentTime;
this._calculatedBeatTimers.add(beat.id);
}
this._currentTime += MidiUtils.ticksToMillis(audioDuration, tempoOnBeatStart);

// in case of normal playback register playback
if (realBar === beat.voice.bar) {
this.tickLookup.addBeat(beat, beatStart, audioDuration);
Expand Down
12 changes: 12 additions & 0 deletions src/model/Beat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,18 @@ export class Beat {
*/
public rasgueado: Rasgueado = Rasgueado.None;

/**
* Whether to show the time when this beat is played the first time.
* (requires that the midi for the song is generated so that times are calculated).
* If no midi is generated the timer value might be filled from the input file (or manually).
*/
public showTimer:boolean = false;

/**
* The absolute time in milliseconds when this beat will be played the first time.
*/
public timer: number | null = null;

public addWhammyBarPoint(point: BendPoint): void {
let points = this.whammyBarPoints;
if (points === null) {
Expand Down
3 changes: 2 additions & 1 deletion src/platform/javascript/Html5Canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export class Html5Canvas implements ICanvas {
}

public strokeRect(x: number, y: number, w: number, h: number): void {
this._context.strokeRect(x | 0, y | 0, w, h);
const blurOffset = this.lineWidth % 2 === 0 ? 0 : 0.5;
this._context.strokeRect((x | 0) + blurOffset, (y | 0) + blurOffset, w, h);
}

public beginPath(): void {
Expand Down
3 changes: 2 additions & 1 deletion src/platform/skia/SkiaCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ export class SkiaCanvas implements ICanvas {
}

public strokeRect(x: number, y: number, w: number, h: number): void {
this._canvas.strokeRect(((x * this._scale) | 0), ((y * this._scale) | 0), w * this._scale, h * this._scale);
const blurOffset = this.lineWidth % 2 === 0 ? 0 : 0.5;
this._canvas.strokeRect(((x * this._scale) | 0) + blurOffset, ((y * this._scale) | 0) + blurOffset, w * this._scale, h * this._scale);
}

public beginPath(): void {
Expand Down
Loading