Skip to content
Draft
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
3 changes: 2 additions & 1 deletion packages/alphatab/src/AlphaTabApiBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2434,7 +2434,8 @@ export class AlphaTabApiBase<TSettings> {
this._isInitialBeatCursorUpdate ||
barBounds.y !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.y ||
startBeatX < previousBeatBounds.onNotesX ||
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1;
barBoundings.index > previousBeatBounds.barBounds.masterBarBounds.index + 1 ||
barBounds.h !== previousBeatBounds.barBounds.masterBarBounds.visualBounds.h;

if (jumpCursor) {
cursorHandler.placeBeatCursor(beatCursor, beatBoundings, startBeatX);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ export class AlphaTabWebWorker {
break;
case 'alphaTab.renderScore':
this._updateFontSizes(data.fontSizes);
const renderHints:RenderHints = data.renderHints;
const score: any =
data.score == null ? null : JsonConverter.jsObjectToScore(data.score, this._renderer.settings);
this._renderMultiple(score, data.trackIndexes);
this._renderMultiple(score, data.trackIndexes, renderHints);
break;
case 'alphaTab.updateSettings':
this._updateSettings(data.settings);
Expand Down
8 changes: 8 additions & 0 deletions packages/alphatab/src/rendering/IScoreRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export interface RenderHints {
* internally it might still be decided to clear the viewport.
*/
reuseViewport?: boolean;

/**
* Indicates the index of the first masterbar which was modified in the data model.
* @remarks
* AlphaTab will try to optimize the rendering and other updates to keep unchanged parts.
* At this point only the rendering is affected and the generated MIDI has to be updated separately.
*/
firstChangedMasterBar?: number;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ export class HorizontalScreenLayout extends ScoreLayout {
public doResize(): void {
// not supported
}

public override doUpdateForBars(_renderHints: RenderHints): boolean {
// not supported yet, modifications likely cause anyhow full updates
// as we do not optimize effect bands yet. with effect bands being more
// isolated in bars we could try updating dynamically
return false;
}


protected doLayoutAndRender(renderHints: RenderHints | undefined): void {
const score: Score = this.renderer.score!;
Expand Down Expand Up @@ -150,7 +158,7 @@ export class HorizontalScreenLayout extends ScoreLayout {
}

this.height = this.layoutAndRenderBottomScoreInfo(this.height);
this.height = this.layoutAndRenderAnnotation(this.height);
this.height = this._layoutAndRenderAnnotation(this.height);

this.height += this.pagePadding![3];

Expand Down
43 changes: 29 additions & 14 deletions packages/alphatab/src/rendering/layout/ScoreLayout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,11 @@ import { Lazy } from '@coderline/alphatab/util/Lazy';

/**
* @internal
* @record
*/
class LazyPartial {
public args: RenderFinishedEventArgs;
public renderCallback: (canvas: ICanvas) => void;
public constructor(args: RenderFinishedEventArgs, renderCallback: (canvas: ICanvas) => void) {
this.args = args;
this.renderCallback = renderCallback;
}
interface LazyPartial {
args: RenderFinishedEventArgs;
renderCallback: (canvas: ICanvas) => void;
}

/**
Expand Down Expand Up @@ -84,13 +81,10 @@ export abstract class ScoreLayout {
}
public abstract doResize(): void;

public abstract doUpdateForBars(renderHints: RenderHints): boolean;

public layoutAndRender(renderHints?: RenderHints): void {
this._lazyPartials.clear();
this.slurRegistry.clear();
this.beamingRuleLookups.clear();
this._barRendererLookup.clear();

this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile)!;

const score: Score = this.renderer.score!;

Expand All @@ -102,6 +96,19 @@ export abstract class ScoreLayout {
this.lastBarIndex
);

const firstChangedMasterBar = renderHints?.firstChangedMasterBar;
if (firstChangedMasterBar !== undefined) {
if (this.doUpdateForBars(renderHints!)) {
return;
}
}

this._lazyPartials.clear();
this.beamingRuleLookups.clear();
this._barRendererLookup.clear();

this.profile = Environment.staveProfiles.get(this.renderer.settings.display.staveProfile)!;

this.pagePadding = this.renderer.settings.display.padding.map(p => p / this.renderer.settings.display.scale);
if (!this.pagePadding) {
this.pagePadding = [0, 0, 0, 0];
Expand All @@ -118,6 +125,10 @@ export abstract class ScoreLayout {

private _lazyPartials: Map<string, LazyPartial> = new Map<string, LazyPartial>();

protected getExistingPartialArgs(id:string): RenderFinishedEventArgs|undefined {
return this._lazyPartials.has(id) ? this._lazyPartials.get(id)!.args : undefined;
}

protected registerPartial(args: RenderFinishedEventArgs, callback: (canvas: ICanvas) => void) {
if (args.height === 0) {
return;
Expand All @@ -137,7 +148,11 @@ export abstract class ScoreLayout {
this._internalRenderLazyPartial(args, callback);
} else {
// in case of lazy loading -> first register lazy, then notify
this._lazyPartials.set(args.id, new LazyPartial(args, callback));
const partial: LazyPartial = {
args,
renderCallback: callback
};
this._lazyPartials.set(args.id, partial);
(this.renderer.partialLayoutFinished as EventEmitterOfT<RenderFinishedEventArgs>).trigger(args);
}
}
Expand Down Expand Up @@ -500,7 +515,7 @@ export abstract class ScoreLayout {
}
}

public layoutAndRenderAnnotation(y: number): number {
protected _layoutAndRenderAnnotation(y: number): number {
// attention, you are not allowed to remove change this notice within any version of this library without permission!
const msg: string = 'rendered by alphaTab';
const resources: RenderingResources = this.renderer.settings.display.resources;
Expand Down
80 changes: 73 additions & 7 deletions packages/alphatab/src/rendering/layout/VerticalLayoutBase.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { EventEmitterOfT } from '@coderline/alphatab/EventEmitter';
import { Logger } from '@coderline/alphatab/Logger';
import { ScoreSubElement } from '@coderline/alphatab/model/Score';
import { type ICanvas, TextAlign } from '@coderline/alphatab/platform/ICanvas';
import type { RenderingResources } from '@coderline/alphatab/RenderingResources';
import type { TextGlyph } from '@coderline/alphatab/rendering/glyphs/TextGlyph';
import type { RenderHints } from '@coderline/alphatab/rendering/IScoreRenderer';
import { ScoreLayout } from '@coderline/alphatab/rendering/layout/ScoreLayout';
import { RenderFinishedEventArgs } from '@coderline/alphatab/rendering/RenderFinishedEventArgs';
import type { MasterBarsRenderers } from '@coderline/alphatab/rendering/staves/MasterBarsRenderers';
import type { StaffSystem } from '@coderline/alphatab/rendering/staves/StaffSystem';
import type { RenderingResources } from '@coderline/alphatab/RenderingResources';

/**
* Base layout for page and parchment style layouts where we have an endless
Expand All @@ -21,11 +22,18 @@ export abstract class VerticalLayoutBase extends ScoreLayout {

private _reuseViewPort: boolean = false;

private _preSystemPartialIds: string[] = [];
private _systemPartialIds: string[] = [];

protected doLayoutAndRender(renderHints: RenderHints | undefined): void {
let y: number = this.pagePadding![1];
this.width = this.renderer.width;
this._allMasterBarRenderers = [];
this._preSystemPartialIds = [];
this._systemPartialIds = [];

this._reuseViewPort = renderHints?.reuseViewport ?? false;
this._systems = [];

//
// 1. Score Info
Expand All @@ -38,11 +46,11 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
y = this._layoutAndRenderChordDiagrams(y, -1);
//
// 4. One result per StaffSystem
y = this._layoutAndRenderScore(y);
y = this._layoutAndRenderScore(y, this.firstBarIndex);

y = this.layoutAndRenderBottomScoreInfo(y);

y = this.layoutAndRenderAnnotation(y);
y = this._layoutAndRenderAnnotation(y);

this.height = (y + this.pagePadding![3]) * this.renderer.settings.display.scale;
}
Expand All @@ -52,6 +60,15 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
super.registerPartial(args, callback);
}

protected reregisterPartial(id: string) {
const args = this.getExistingPartialArgs(id);
if (!args) {
return;
}
args.reuseViewport = this._reuseViewPort;
(this.renderer.partialLayoutFinished as EventEmitterOfT<RenderFinishedEventArgs>).trigger(args);
}

public get supportsResize(): boolean {
return true;
}
Expand All @@ -64,6 +81,52 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
return x;
}

public override doUpdateForBars(renderHints: RenderHints): boolean {
this._reuseViewPort = renderHints.reuseViewport ?? false;
const firstModifiedMasterBar = renderHints.firstChangedMasterBar!;

// first update existing systems as needed
const systemIndex = this._systems.findIndex(s => {
const first = s.masterBarsRenderers[0].masterBar.index;
const last = s.masterBarsRenderers[s.masterBarsRenderers.length - 1].masterBar.index;
return first <= firstModifiedMasterBar && firstModifiedMasterBar <= last;
});

if (systemIndex === -1 || !this.renderer.settings.core.enableLazyLoading) {
return false;
}

// for now we do a full relayout from the first modified masterbar
// there is a lot of room for even more performant updates, but they come
// at a risk that features break.
// e.g. we could only shift systems where the content didn't change,
// but we might still have ties/slurs which have to be updated.
const removeSystems = this._systems.splice(systemIndex, this._systems.length - systemIndex);
this._systemPartialIds.splice(systemIndex, this._systemPartialIds.length - systemIndex);
const system = removeSystems[0];
let y = system.y;
const firstBarIndex = system.masterBarsRenderers[0].masterBar.index;

// signal all partials which didn't change
for (const preSystemPartial of this._preSystemPartialIds) {
this.reregisterPartial(preSystemPartial);
}
for (let i = 0; i < systemIndex; i++) {
this.reregisterPartial(this._systemPartialIds[i]);
}

// new partials for all other prats
y = this._layoutAndRenderScore(y, firstBarIndex);

y = this.layoutAndRenderBottomScoreInfo(y);

y = this._layoutAndRenderAnnotation(y);

this.height = (y + this.pagePadding![3]) * this.renderer.settings.display.scale;

return true;
}

public doResize(): void {
let y: number = this.pagePadding![1];
this.width = this.renderer.width;
Expand All @@ -85,7 +148,7 @@ export abstract class VerticalLayoutBase extends ScoreLayout {

y = this.layoutAndRenderBottomScoreInfo(y);

y = this.layoutAndRenderAnnotation(y);
y = this._layoutAndRenderAnnotation(y);

this.height = (y + this.pagePadding![3]) * this.renderer.settings.display.scale;
}
Expand Down Expand Up @@ -115,6 +178,7 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
canvas.textAlign = TextAlign.Center;
this.tuningGlyph!.paint(0, 0, canvas);
});
this._preSystemPartialIds.push(e.id);

return y + tuningHeight;
}
Expand Down Expand Up @@ -143,6 +207,7 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
canvas.textAlign = TextAlign.Center;
this.chordDiagrams!.paint(0, 0, canvas);
});
this._preSystemPartialIds.push(e.id);

return y + diagramHeight;
}
Expand Down Expand Up @@ -197,6 +262,7 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
g.paint(0, 0, canvas);
}
});
this._preSystemPartialIds.push(e.id);
}

return y + infoHeight;
Expand All @@ -205,6 +271,7 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
private _resizeAndRenderScore(y: number, oldHeight: number): number {
// if we have a fixed number of bars per row, we only need to refit them.
const barsPerRowActive = this.getBarsPerSystem(0) > 0;
this._systemPartialIds = [];
if (barsPerRowActive) {
for (let i: number = 0; i < this._systems.length; i++) {
const system: StaffSystem = this._systems[i];
Expand Down Expand Up @@ -270,12 +337,10 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
return y;
}

private _layoutAndRenderScore(y: number): number {
const startIndex: number = this.firstBarIndex;
private _layoutAndRenderScore(y: number, startIndex: number): number {
let currentBarIndex: number = startIndex;
const endBarIndex: number = this.lastBarIndex;

this._systems = [];
while (currentBarIndex <= endBarIndex) {
// create system and align set proper coordinates
const system: StaffSystem = this._createStaffSystem(currentBarIndex, endBarIndex);
Expand Down Expand Up @@ -317,6 +382,7 @@ export abstract class VerticalLayoutBase extends ScoreLayout {
// since we use partial drawing
system.paint(0, -(args.y / this.renderer.settings.display.scale), canvas);
});
this._systemPartialIds.push(args.id);

// calculate coordinates for next system
return height;
Expand Down
14 changes: 14 additions & 0 deletions packages/csharp/src/AlphaTab/Core/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@
return list.FirstOrDefault(predicate);
}

public static T FindIndex<T>(this IList<T> list, Func<T, bool> predicate)
{
var index = 0;
foreach (var item in list)
{
if (predicate(item))
{
return index;

Check failure on line 80 in packages/csharp/src/AlphaTab/Core/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / Build and Test C#

Cannot implicitly convert type 'int' to 'T'

Check failure on line 80 in packages/csharp/src/AlphaTab/Core/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / Build and Test C#

Cannot implicitly convert type 'int' to 'T'
}
index++;
}
return -1;

Check failure on line 84 in packages/csharp/src/AlphaTab/Core/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / Build and Test C#

Cannot implicitly convert type 'int' to 'T'

Check failure on line 84 in packages/csharp/src/AlphaTab/Core/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / Build and Test C#

Cannot implicitly convert type 'int' to 'T'
}

public static bool Includes<T>(this IList<T> list, T item)
{
return list.Contains(item);
Expand Down
Loading
Loading