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
89 changes: 73 additions & 16 deletions packages/alphatab/src/AlphaTabApiBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2863,7 +2863,7 @@ export class AlphaTabApiBase<TSettings> {
return;
}

if (this._hasCursor && this.settings.player.enableUserInteraction) {
if (this._hasCursor && this.settings.player.enablePlaybackRangeSelection) {
this._selectionStart = { beat };
this._selectionEnd = undefined;
}
Expand All @@ -2887,10 +2887,14 @@ export class AlphaTabApiBase<TSettings> {
return;
}

if (this.settings.player.enableUserInteraction) {
if (this.settings.player.enablePlaybackRangeSelection) {
if (!this._selectionEnd || this._selectionEnd.beat !== beat) {
this._selectionEnd = { beat };
this._cursorSelectRange(this._selectionStart, this._selectionEnd);
// ensure we do not clear the selection until we have different beats
const hasRange = this._selectionStart?.beat !== beat;
if (hasRange) {
this._selectionEnd = { beat };
this._cursorSelectRange(this._selectionStart, this._selectionEnd);
}
}
}
(this.beatMouseMove as EventEmitterOfT<Beat>).trigger(beat);
Expand All @@ -2911,15 +2915,52 @@ export class AlphaTabApiBase<TSettings> {
return;
}

if (this._hasCursor && this.settings.player.enableUserInteraction) {
this.applyPlaybackRangeFromHighlight();
if (this._hasCursor) {
let shouldSeekToBeat = beat && this.settings.player.enableSeekToClick;
if (this.settings.player.enablePlaybackRangeSelection) {
if (this._internalApplyPlaybackRangeFromHighlight()) {
shouldSeekToBeat = false;
}
}

if (shouldSeekToBeat) {
this._seekToBeat(beat!);
}
}

(this.beatMouseUp as EventEmitterOfT<Beat | null>).trigger(beat);
this.uiFacade.triggerEvent(this.container, 'beatMouseUp', beat, originalEvent);
this._isBeatMouseDown = false;
}

private _seekToBeat(beat: Beat) {
const tickCache = this._tickCache;
if (!tickCache) {
return;
}

this._currentBeat = null;
const realStartMasterBarStart: number = tickCache.getMasterBarStart(beat.voice.bar.masterBar);
const startBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(beat);
const startBeatPlaybackStart = startBeatPlaybackRange?.startTick ?? beat.playbackStart;

// clamp to playback range
let beatTick = realStartMasterBarStart + startBeatPlaybackStart;
const playbackRange = this.playbackRange;
if (playbackRange) {
if (beatTick < playbackRange.startTick) {
beatTick = playbackRange.startTick;
} else if (beatTick > playbackRange.endTick) {
beatTick = playbackRange.endTick;
}
}

if (this._player.state === PlayerState.Paused) {
this._cursorUpdateTick(beatTick, false, 1);
}
this.tickPosition = beatTick;
}

private _onNoteMouseUp(originalEvent: IMouseEventArgs, note: Note | null): void {
if (this._isDestroyed) {
return;
Expand Down Expand Up @@ -2952,7 +2993,7 @@ export class AlphaTabApiBase<TSettings> {
if (!e.isLeftMouseButton) {
return;
}
if (this.settings.player.enableUserInteraction) {
if (this.settings.player.enablePlaybackRangeSelection || this.settings.player.enableSeekToClick) {
e.preventDefault();
}
const relX: number = e.getX(this.canvasElement);
Expand Down Expand Up @@ -2991,7 +3032,7 @@ export class AlphaTabApiBase<TSettings> {
if (!this._isBeatMouseDown) {
return;
}
if (this.settings.player.enableUserInteraction) {
if (this.settings.player.enablePlaybackRangeSelection || this.settings.player.enableSeekToClick) {
e.preventDefault();
}
const relX: number = e.getX(this.canvasElement);
Expand All @@ -3009,7 +3050,7 @@ export class AlphaTabApiBase<TSettings> {
}
});
this._renderer.postRenderFinished.on(() => {
if (!this._selectionStart || !this._hasCursor || !this.settings.player.enableUserInteraction) {
if (!this._selectionStart || !this._hasCursor || !this.settings.player.enablePlaybackRangeSelection) {
return;
}
this._cursorSelectRange(this._selectionStart, this._selectionEnd);
Expand Down Expand Up @@ -3105,6 +3146,10 @@ export class AlphaTabApiBase<TSettings> {
* ```
*/
public applyPlaybackRangeFromHighlight() {
this._internalApplyPlaybackRangeFromHighlight();
}

private _internalApplyPlaybackRangeFromHighlight() {
if (this._selectionEnd) {
const startTick: number =
this._tickCache?.getBeatStart(this._selectionStart!.beat) ??
Expand All @@ -3117,7 +3162,11 @@ export class AlphaTabApiBase<TSettings> {
this._selectionStart = this._selectionEnd;
this._selectionEnd = t;
}
} else if (!this.settings.player.resetPlaybackRangeOnClick) {
// no new range selected -> do not reset playback range if respective option is set
return false;
}

if (this._selectionStart && this._tickCache) {
// get the start and stop ticks (which consider properly repeats)
const tickCache: MidiTickLookup = this._tickCache;
Expand All @@ -3126,14 +3175,12 @@ export class AlphaTabApiBase<TSettings> {
);
const startBeatPlaybackRange = tickCache.getRelativeBeatPlaybackRange(this._selectionStart.beat);
const startBeatPlaybackStart = startBeatPlaybackRange?.startTick ?? this._selectionStart.beat.playbackStart;
// move to selection start
this._currentBeat = null; // reset current beat so it is updating the cursor
if (this._player.state === PlayerState.Paused) {
this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
}
this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;

let seekToStart = this.settings.player.enableSeekToClick;

// set playback range
if (this._selectionEnd && this._selectionStart.beat !== this._selectionEnd.beat) {
seekToStart = true;
const realEndMasterBarStart: number = tickCache.getMasterBarStart(
this._selectionEnd.beat.voice.bar.masterBar
);
Expand All @@ -3145,12 +3192,22 @@ export class AlphaTabApiBase<TSettings> {
range.startTick = realStartMasterBarStart + startBeatPlaybackStart;
range.endTick = realEndMasterBarStart + endBeatPlaybackEnd - 50;
this.playbackRange = range;
} else {
} else if (this.settings.player.resetPlaybackRangeOnClick) {
this._selectionStart = undefined;
this.playbackRange = null;
this._cursorSelectRange(this._selectionStart, this._selectionEnd);
}

if (seekToStart) {
this._currentBeat = null; // reset current beat so it is updating the cursor
if (this._player.state === PlayerState.Paused) {
this._cursorUpdateTick(realStartMasterBarStart + startBeatPlaybackStart, false, 1);
}
this.tickPosition = realStartMasterBarStart + startBeatPlaybackStart;
}
}

return true;
}

/**
Expand Down
40 changes: 39 additions & 1 deletion packages/alphatab/src/PlayerSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,49 @@ export class PlayerSettings {
* @since 0.9.7
* @defaultValue `true`
* @category Player
* @json_read_only
* @remarks
* This setting configures whether alphaTab provides the default user interaction features like selection of the playback range and "seek on click".
* By default users can select the desired playback range with the mouse and also jump to individual beats by click. This behavior can be contolled with this setting.
* @deprecated Use {@link enableSeekToClick} and {@link enablePlaybackRangeSelection} individually
*/
public get enableUserInteraction(): boolean {
return this.enableSeekToClick || this.enablePlaybackRangeSelection;
}

/**
* @deprecated Use {@link enableSeekToClick} and {@link enablePlaybackRangeSelection} individually
*/
public set enableUserInteraction(value: boolean) {
this.enableSeekToClick = value;
this.enablePlaybackRangeSelection = value;
this.resetPlaybackRangeOnClick = value;
}

/**
* Whether the a click on the music sheet triggers a player seek to the note/beat at the
* clicked location.
* @since 1.9.0
* @defaultValue `true`
* @category Player
*/
public enableSeekToClick: boolean = true;

/**
* Whether user click and drag results in a selection defining the playback range.
* @since 1.9.0
* @defaultValue `true`
* @category Player
*/
public enablePlaybackRangeSelection: boolean = true;

/**
* Whether a simple click (no range drag) should reset the current playback range.
* @since 1.9.0
* @defaultValue `true`
* @category Player
*/
public enableUserInteraction: boolean = true;
public resetPlaybackRangeOnClick: boolean = true;

/**
* The X-offset to add when scrolling.
Expand Down
24 changes: 24 additions & 0 deletions packages/alphatab/src/generated/PlayerSettingsJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,35 @@ export interface PlayerSettingsJson {
* @since 0.9.7
* @defaultValue `true`
* @category Player
* @json_read_only
* @remarks
* This setting configures whether alphaTab provides the default user interaction features like selection of the playback range and "seek on click".
* By default users can select the desired playback range with the mouse and also jump to individual beats by click. This behavior can be contolled with this setting.
* @deprecated Use {@link enableSeekToClick} and {@link enablePlaybackRangeSelection} individually
*/
enableUserInteraction?: boolean;
/**
* Whether the a click on the music sheet triggers a player seek to the note/beat at the
* clicked location.
* @since 1.9.0
* @defaultValue `true`
* @category Player
*/
enableSeekToClick?: boolean;
/**
* Whether user click and drag results in a selection defining the playback range.
* @since 1.9.0
* @defaultValue `true`
* @category Player
*/
enablePlaybackRangeSelection?: boolean;
/**
* Whether a simple click (no range drag) should reset the current playback range.
* @since 1.9.0
* @defaultValue `true`
* @category Player
*/
resetPlaybackRangeOnClick?: boolean;
/**
* The X-offset to add when scrolling.
* @since 0.9.6
Expand Down
13 changes: 12 additions & 1 deletion packages/alphatab/src/generated/PlayerSettingsSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export class PlayerSettingsSerializer {
o.set("enablecursor", obj.enableCursor);
o.set("enableanimatedbeatcursor", obj.enableAnimatedBeatCursor);
o.set("enableelementhighlighting", obj.enableElementHighlighting);
o.set("enableuserinteraction", obj.enableUserInteraction);
o.set("enableseektoclick", obj.enableSeekToClick);
o.set("enableplaybackrangeselection", obj.enablePlaybackRangeSelection);
o.set("resetplaybackrangeonclick", obj.resetPlaybackRangeOnClick);
o.set("scrolloffsetx", obj.scrollOffsetX);
o.set("scrolloffsety", obj.scrollOffsetY);
o.set("scrollmode", obj.scrollMode as number);
Expand Down Expand Up @@ -81,6 +83,15 @@ export class PlayerSettingsSerializer {
case "enableuserinteraction":
obj.enableUserInteraction = v! as boolean;
return true;
case "enableseektoclick":
obj.enableSeekToClick = v! as boolean;
return true;
case "enableplaybackrangeselection":
obj.enablePlaybackRangeSelection = v! as boolean;
return true;
case "resetplaybackrangeonclick":
obj.resetPlaybackRangeOnClick = v! as boolean;
return true;
case "scrolloffsetx":
obj.scrollOffsetX = v! as number;
return true;
Expand Down
3 changes: 2 additions & 1 deletion packages/alphatab/src/platform/javascript/AlphaTabApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ export class AlphaTabApi extends AlphaTabApiBase<SettingsJson | Settings> {
settings.player.enableCursor = false;
settings.player.playerMode = PlayerMode.Disabled;
settings.player.enableElementHighlighting = false;
settings.player.enableUserInteraction = false;
settings.player.enableSeekToClick = false;
settings.player.enablePlaybackRangeSelection = false;
settings.player.soundFont = null;
settings.display.scale = 0.8;
settings.display.stretchForce = 0.8;
Expand Down
Loading