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
29 changes: 17 additions & 12 deletions packages/alphatab/src/AlphaTabApiBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import type { IMouseEventArgs } from '@coderline/alphatab/platform/IMouseEventAr
import type { IUiFacade } from '@coderline/alphatab/platform/IUiFacade';
import { ResizeEventArgs } from '@coderline/alphatab/ResizeEventArgs';
import { BeatContainerGlyph } from '@coderline/alphatab/rendering/glyphs/BeatContainerGlyph';
import type { IScoreRenderer } from '@coderline/alphatab/rendering/IScoreRenderer';
import type { IScoreRenderer, RenderHints } from '@coderline/alphatab/rendering/IScoreRenderer';
import type { RenderFinishedEventArgs } from '@coderline/alphatab/rendering/RenderFinishedEventArgs';
import { ScoreRenderer } from '@coderline/alphatab/rendering/ScoreRenderer';
import { ScoreRendererWrapper } from '@coderline/alphatab/rendering/ScoreRendererWrapper';
Expand Down Expand Up @@ -631,6 +631,7 @@ export class AlphaTabApiBase<TSettings> {
* @param score The score containing the tracks to be rendered.
* @param trackIndexes The indexes of the tracks from the song that should be rendered. If not provided, the first track of the
* song will be shown.
* @param renderHints Additional hints to respect during layouting and rendering.
*
* @category Methods - Core
* @since 0.9.4
Expand All @@ -655,7 +656,7 @@ export class AlphaTabApiBase<TSettings> {
* api.renderScore(generateScore(), alphaTab.collections.DoubleList(2, 3));
* ```
*/
public renderScore(score: Score, trackIndexes?: number[]): void {
public renderScore(score: Score, trackIndexes?: number[], renderHints?: RenderHints): void {
const tracks: Track[] = [];
if (!trackIndexes) {
if (score.tracks.length > 0) {
Expand All @@ -678,12 +679,13 @@ export class AlphaTabApiBase<TSettings> {
}
}
}
this._internalRenderTracks(score, tracks);
this._internalRenderTracks(score, tracks, renderHints);
}

/**
* Renders the given list of tracks.
* @param tracks The tracks to render. They must all belong to the same score.
* @param renderHints Additional hints to respect during layouting and rendering.
*
* @category Methods - Core
* @since 0.9.4
Expand Down Expand Up @@ -714,7 +716,7 @@ export class AlphaTabApiBase<TSettings> {
* }
* ```
*/
public renderTracks(tracks: Track[]): void {
public renderTracks(tracks: Track[], renderHints?: RenderHints): void {
if (tracks.length > 0) {
const score: Score = tracks[0].score;
for (const track of tracks) {
Expand All @@ -728,11 +730,11 @@ export class AlphaTabApiBase<TSettings> {
return;
}
}
this._internalRenderTracks(score, tracks);
this._internalRenderTracks(score, tracks, renderHints);
}
}

private _internalRenderTracks(score: Score, tracks: Track[]): void {
private _internalRenderTracks(score: Score, tracks: Track[], renderHints: RenderHints | undefined): void {
ModelUtils.applyPitchOffsets(this.settings, score);
if (score !== this.score) {
this._score = score;
Expand All @@ -745,7 +747,7 @@ export class AlphaTabApiBase<TSettings> {
this._trackIndexLookup = new Set<number>(this._trackIndexes);
this._onScoreLoaded(score);
this.loadMidiForScore();
this.render();
this.render(renderHints);
} else {
this._tracks = tracks;

Expand All @@ -761,7 +763,7 @@ export class AlphaTabApiBase<TSettings> {
}
this._trackIndexLookup = new Set<number>(this._trackIndexes);

this.render();
this.render(renderHints);
}
}

Expand Down Expand Up @@ -949,6 +951,7 @@ export class AlphaTabApiBase<TSettings> {

/**
* Initiates a re-rendering of the current setup.
* @param renderHints Additional hints to respect during layouting and rendering.
* @remarks
* If rendering is not yet possible, it will be deferred until the UI changes to be ready for rendering.
*
Expand All @@ -975,13 +978,13 @@ export class AlphaTabApiBase<TSettings> {
* api.render()
* ```
*/
public render(): void {
public render(renderHints?: RenderHints): void {
if (this.uiFacade.canRender) {
// when font is finally loaded, start rendering
this._renderer.width = this.container.width;
this._renderer.renderScore(this.score, this._trackIndexes);
this._renderer.renderScore(this.score, this._trackIndexes, renderHints);
} else {
this.uiFacade.canRenderChanged.on(() => this.render());
this.uiFacade.canRenderChanged.on(() => this.render(renderHints));
}
}

Expand Down Expand Up @@ -2379,7 +2382,9 @@ export class AlphaTabApiBase<TSettings> {
if (shouldScroll && !this._isBeatMouseDown && this.settings.player.scrollMode !== ScrollMode.Off) {
const handler = this.customScrollHandler ?? this._defaultScrollHandler;
if (handler) {
handler.onBeatCursorUpdating(beatBoundings, nextBeatBoundings === null ? undefined : nextBeatBoundings,
handler.onBeatCursorUpdating(
beatBoundings,
nextBeatBoundings === null ? undefined : nextBeatBoundings,
cursorMode,
startBeatX,
nextBeatX,
Expand Down
15 changes: 8 additions & 7 deletions packages/alphatab/src/platform/javascript/AlphaTabWebWorker.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Environment } from '@coderline/alphatab/Environment';
import { SettingsSerializer } from '@coderline/alphatab/generated/SettingsSerializer';
import { Logger } from '@coderline/alphatab/Logger';
import { JsonConverter } from '@coderline/alphatab/model/JsonConverter';
import type { Score } from '@coderline/alphatab/model/Score';
import type { IWorkerScope } from '@coderline/alphatab/platform/javascript/IWorkerScope';
import { type FontSizeDefinition, FontSizes } from '@coderline/alphatab/platform/svg/FontSizes';
import type { RenderHints } from '@coderline/alphatab/rendering/IScoreRenderer';
import { ScoreRenderer } from '@coderline/alphatab/rendering/ScoreRenderer';
import type { Settings } from '@coderline/alphatab/Settings';
import { Logger } from '@coderline/alphatab/Logger';
import { Environment } from '@coderline/alphatab/Environment';
import { SettingsSerializer } from '@coderline/alphatab/generated/SettingsSerializer';

/**
* @target web
Expand Down Expand Up @@ -67,8 +68,8 @@ export class AlphaTabWebWorker {
});
this._renderer.error.on(this._error.bind(this));
break;
case 'alphaTab.invalidate':
this._renderer.render();
case 'alphaTab.render':
this._renderer.render(data.renderHints);
break;
case 'alphaTab.resizeRender':
this._renderer.resizeRender();
Expand Down Expand Up @@ -111,9 +112,9 @@ export class AlphaTabWebWorker {
SettingsSerializer.fromJson(this._renderer.settings, json);
}

private _renderMultiple(score: Score | null, trackIndexes: number[] | null): void {
private _renderMultiple(score: Score | null, trackIndexes: number[] | null, renderHints?: RenderHints): void {
try {
this._renderer.renderScore(score, trackIndexes);
this._renderer.renderScore(score, trackIndexes, renderHints);
} catch (e) {
this._error(e as Error);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import type { AlphaTabApiBase } from '@coderline/alphatab/AlphaTabApiBase';
import { EventEmitter, type IEventEmitterOfT, type IEventEmitter, EventEmitterOfT } from '@coderline/alphatab/EventEmitter';
import {
EventEmitter,
type IEventEmitterOfT,
type IEventEmitter,
EventEmitterOfT
} from '@coderline/alphatab/EventEmitter';
import { JsonConverter } from '@coderline/alphatab/model/JsonConverter';
import type { Score } from '@coderline/alphatab/model/Score';
import { FontSizes } from '@coderline/alphatab/platform/svg/FontSizes';
import type { IScoreRenderer } from '@coderline/alphatab/rendering/IScoreRenderer';
import type { IScoreRenderer, RenderHints } from '@coderline/alphatab/rendering/IScoreRenderer';
import type { RenderFinishedEventArgs } from '@coderline/alphatab/rendering/RenderFinishedEventArgs';
import { BoundsLookup } from '@coderline/alphatab/rendering/utils/BoundsLookup';
import type { Settings } from '@coderline/alphatab/Settings';
Expand Down Expand Up @@ -55,9 +60,10 @@ export class AlphaTabWorkerScoreRenderer<T> implements IScoreRenderer {
return jsObject;
}

public render(): void {
public render(renderHints?: RenderHints): void {
this._worker.postMessage({
cmd: 'alphaTab.render'
cmd: 'alphaTab.render',
renderHints: renderHints
});
}

Expand Down Expand Up @@ -113,13 +119,15 @@ export class AlphaTabWorkerScoreRenderer<T> implements IScoreRenderer {
}
}

public renderScore(score: Score | null, trackIndexes: number[] | null): void {
const jsObject: unknown = score == null ? null : JsonConverter.scoreToJsObject(Environment.prepareForPostMessage(score));
public renderScore(score: Score | null, trackIndexes: number[] | null, renderHints?: RenderHints): void {
const jsObject: unknown =
score == null ? null : JsonConverter.scoreToJsObject(Environment.prepareForPostMessage(score));
this._worker.postMessage({
cmd: 'alphaTab.renderScore',
score: jsObject,
trackIndexes: Environment.prepareForPostMessage(trackIndexes),
fontSizes: FontSizes.fontSizeLookupTables
fontSizes: FontSizes.fontSizeLookupTables,
renderHints
});
}

Expand All @@ -128,7 +136,8 @@ export class AlphaTabWorkerScoreRenderer<T> implements IScoreRenderer {
new EventEmitterOfT<RenderFinishedEventArgs>();
public readonly partialLayoutFinished: IEventEmitterOfT<RenderFinishedEventArgs> =
new EventEmitterOfT<RenderFinishedEventArgs>();
public readonly renderFinished: IEventEmitterOfT<RenderFinishedEventArgs> = new EventEmitterOfT<RenderFinishedEventArgs>();
public readonly renderFinished: IEventEmitterOfT<RenderFinishedEventArgs> =
new EventEmitterOfT<RenderFinishedEventArgs>();
public readonly postRenderFinished: IEventEmitter = new EventEmitter();
public readonly error: IEventEmitterOfT<Error> = new EventEmitterOfT<Error>();
}
3 changes: 3 additions & 0 deletions packages/alphatab/src/platform/javascript/BrowserUiFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,9 @@ export class BrowserUiFacade implements IUiFacade<unknown> {
placeholder.renderedResultId = undefined;
placeholder.renderedResult = undefined;

if (!renderResult.reuseViewport) {
placeholder.textContent = '';
}
this._resultIdToElementLookup.set(renderResult.id, placeholder);

// remember which bar is contained in which node for faster lookup
Expand Down
22 changes: 20 additions & 2 deletions packages/alphatab/src/rendering/IScoreRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,22 @@ import type { RenderFinishedEventArgs } from '@coderline/alphatab/rendering/Rend
import type { BoundsLookup } from '@coderline/alphatab/rendering/utils/BoundsLookup';
import type { Settings } from '@coderline/alphatab/Settings';

/**
* Additional hints which should be respected during layout and rendering of the score.
* @public
*/
export interface RenderHints {
/**
* A value indicating whether the currently rendered viewport can be reused when rendering the score.
* @remarks
* Set this property to true in cases of live-editing where the rendered score changes from an object perspective,
* but remains the similar from a content perspective. This way the visual update will appear more smooth than a full clearing.
*
* internally it might still be decided to clear the viewport.
*/
reuseViewport?: boolean;
}

/**
* Represents the public interface of the component that can render scores.
* @public
Expand All @@ -31,9 +47,10 @@ export interface IScoreRenderer {

/**
* Initiates a re-rendering of the current setup.
* @param renderHints Additional hints to respect during layouting and rendering.
* @since 0.9.6
*/
render(): void;
render(renderHints?: RenderHints): void;

/**
* Initiates a resize-optimized re-rendering of the score using the current settings.
Expand All @@ -53,9 +70,10 @@ export interface IScoreRenderer {
* Initiates the rendering of the specified tracks of the given score.
* @param score The score defining the tracks.
* @param trackIndexes The indexes of the tracks to draw.
* @param renderHints Additional hints to respect during layouting and rendering.
* @since 0.9.6
*/
renderScore(score: Score | null, trackIndexes: number[] | null): void;
renderScore(score: Score | null, trackIndexes: number[] | null, renderHints?: RenderHints): void;

/**
* Requests the rendering of a chunk which was layed out before.
Expand Down
6 changes: 1 addition & 5 deletions packages/alphatab/src/rendering/LineBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,11 +714,7 @@ export abstract class LineBarRenderer extends BarRendererBase {
stemY2 = y1;
}

if (y1 < y2) {
this.paintBeamingStem(beat, cy + this.y, cx + this.x + stemX, stemY1, stemY2, canvas);
} else {
this.paintBeamingStem(beat, cy + this.y, cx + this.x + stemX, stemY2, stemY1, canvas);
}
this.paintBeamingStem(beat, cy + this.y, cx + this.x + stemX, stemY1, stemY2, canvas);

using _ = ElementStyleHelper.beat(canvas, beamsElement, beat);

Expand Down
14 changes: 14 additions & 0 deletions packages/alphatab/src/rendering/RenderFinishedEventArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ export class RenderFinishedEventArgs {
* Gets or sets the unique id of this event args.
*/
public id: string = ModelUtils.newGuid();

/**
* A value indicating whether the currently rendered viewport can be reused.
* @remarks
* If set to true, the viewport does NOT need to be cleared as a similar
* content will be rendered.
* If set to false, the viewport and any visual partials should be cleared
* as it could lead to UI disturbances otherwise.
*
* The viewport can be typically used on resize renders or if the user supplied
* a rendering hint that the new score is "similar" to the old one (e.g. in case of live-editing).
*/
public reuseViewport: boolean = true;

/**
* Gets or sets the x position of the current rendering result.
*/
Expand Down
21 changes: 13 additions & 8 deletions packages/alphatab/src/rendering/ScoreRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { LayoutMode } from '@coderline/alphatab/LayoutMode';
import { Environment } from '@coderline/alphatab/Environment';
import { EventEmitter, type IEventEmitter, type IEventEmitterOfT, EventEmitterOfT } from '@coderline/alphatab/EventEmitter';
import {
EventEmitter,
type IEventEmitter,
type IEventEmitterOfT,
EventEmitterOfT
} from '@coderline/alphatab/EventEmitter';
import type { Score } from '@coderline/alphatab/model/Score';
import type { Track } from '@coderline/alphatab/model/Track';
import type { ICanvas } from '@coderline/alphatab/platform/ICanvas';
import type { IScoreRenderer } from '@coderline/alphatab/rendering/IScoreRenderer';
import type { IScoreRenderer, RenderHints } from '@coderline/alphatab/rendering/IScoreRenderer';
import type { ScoreLayout } from '@coderline/alphatab/rendering/layout/ScoreLayout';
import { RenderFinishedEventArgs } from '@coderline/alphatab/rendering/RenderFinishedEventArgs';
import { BoundsLookup } from '@coderline/alphatab/rendering/utils/BoundsLookup';
Expand Down Expand Up @@ -70,7 +75,7 @@ export class ScoreRenderer implements IScoreRenderer {
return false;
}

public renderScore(score: Score | null, trackIndexes: number[] | null): void {
public renderScore(score: Score | null, trackIndexes: number[] | null, renderHints?: RenderHints): void {
try {
this.score = score;
let tracks: Track[] | null = null;
Expand All @@ -92,7 +97,7 @@ export class ScoreRenderer implements IScoreRenderer {
}

this.tracks = tracks;
this.render();
this.render(renderHints);
} catch (e) {
(this.error as EventEmitterOfT<Error>).trigger(e as Error);
}
Expand Down Expand Up @@ -130,7 +135,7 @@ export class ScoreRenderer implements IScoreRenderer {
}
}

public render(): void {
public render(renderHints?: RenderHints): void {
if (this.width === 0) {
Logger.warning('Rendering', 'AlphaTab skipped rendering because of width=0 (element invisible)', null);
return;
Expand All @@ -155,7 +160,7 @@ export class ScoreRenderer implements IScoreRenderer {
}
(this.preRender as EventEmitterOfT<boolean>).trigger(false);
this._recreateLayout();
this._layoutAndRender();
this._layoutAndRender(renderHints);
Logger.debug('Rendering', 'Rendering finished');
}
}
Expand All @@ -178,13 +183,13 @@ export class ScoreRenderer implements IScoreRenderer {
Logger.debug('Rendering', 'Resize finished');
}

private _layoutAndRender(): void {
private _layoutAndRender(renderHints?: RenderHints): void {
Logger.debug(
'Rendering',
`Rendering at scale ${this.settings.display.scale} with layout ${this.layout!.name}`,
null
);
this.layout!.layoutAndRender();
this.layout!.layoutAndRender(renderHints);
this._renderedTracks = this.tracks;
this._onRenderFinished();
(this.postRenderFinished as EventEmitter).trigger();
Expand Down
Loading