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
8 changes: 6 additions & 2 deletions packages/alphatab/src/platform/worker/AlphaSynthWebWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ export class AlphaSynthWebWorker {

public handleMessage(e: MessageEvent<IAlphaSynthWorkerMessage>): void {
const data = e.data;
switch (data.cmd) {
const cmd = data.cmd;
if(!cmd) {
return;
}
switch (cmd) {
case 'alphaSynth.initialize':
AlphaSynthWorkerSynthOutput.preferredSampleRate = data.sampleRate;
Logger.logLevel = data.logLevel;
Expand Down Expand Up @@ -137,7 +141,7 @@ export class AlphaSynthWebWorker {
break;
}

if (data.cmd.startsWith('alphaSynth.exporter')) {
if (cmd.startsWith('alphaSynth.exporter')) {
this._handleExporterMessage(e);
}
}
Expand Down
10 changes: 9 additions & 1 deletion packages/alphatab/src/rendering/LineBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -649,8 +649,14 @@ export abstract class LineBarRenderer extends BarRendererBase {
return this.calculateBeamYWithDirection(h, x, this.getBeamDirection(h));
}

protected barNumberGlyph?: BarNumberGlyph;
public get barNumberWidth(): number {
return this.barNumberGlyph?.width ?? 0;
}

protected override createPreBeatGlyphs(): void {
super.createPreBeatGlyphs();
this.barNumberGlyph = undefined;
this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines));
this.createLinePreBeatGlyphs();
let hasSpaceAfterStartGlyphs = false;
Expand All @@ -659,7 +665,9 @@ export abstract class LineBarRenderer extends BarRendererBase {
}

if (this.shouldCreateBarNumber()) {
this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1));
const barNumberGlyph = new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1);
this.barNumberGlyph = barNumberGlyph;
this.addPreBeatGlyph(barNumberGlyph);
} else if (!hasSpaceAfterStartGlyphs) {
this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
}
Expand Down
8 changes: 5 additions & 3 deletions packages/alphatab/src/rendering/NumberedBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { MidiUtils } from '@coderline/alphatab/midi/MidiUtils';
import { BarSubElement } from '@coderline/alphatab/model/Bar';
import { type Beat, BeatSubElement } from '@coderline/alphatab/model/Beat';
import { Duration } from '@coderline/alphatab/model/Duration';
import type { ElementDisplay } from '@coderline/alphatab/model/ElementDisplay';
import { GraceType } from '@coderline/alphatab/model/GraceType';
import { ModelUtils } from '@coderline/alphatab/model/ModelUtils';
import { MusicFontSymbol } from '@coderline/alphatab/model/MusicFontSymbol';
import type { Note } from '@coderline/alphatab/model/Note';
import type { BarNumberDisplay } from '@coderline/alphatab/model/RenderStylesheet';
import type { Voice } from '@coderline/alphatab/model/Voice';
import type { ICanvas } from '@coderline/alphatab/platform/ICanvas';
import { BarNumberDisplay } from '@coderline/alphatab/model/RenderStylesheet';
import { BeatXPosition } from '@coderline/alphatab/rendering/BeatXPosition';
import { BarLineGlyph } from '@coderline/alphatab/rendering/glyphs/BarLineGlyph';
import { BarNumberGlyph } from '@coderline/alphatab/rendering/glyphs/BarNumberGlyph';
Expand All @@ -21,7 +22,6 @@ import { ScoreTimeSignatureGlyph } from '@coderline/alphatab/rendering/glyphs/Sc
import { SpacingGlyph } from '@coderline/alphatab/rendering/glyphs/SpacingGlyph';
import { LineBarRenderer } from '@coderline/alphatab/rendering/LineBarRenderer';
import { NumberedBeatContainerGlyph } from '@coderline/alphatab/rendering/NumberedBeatContainerGlyph';
import type { ElementDisplay } from '@coderline/alphatab/model/ElementDisplay';
import { StaffDisplayResolver } from '@coderline/alphatab/rendering/staves/StaffDisplayResolver';
import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection';
import type { BeamingHelper, BeamingHelperDrawInfo } from '@coderline/alphatab/rendering/utils/BeamingHelper';
Expand Down Expand Up @@ -272,7 +272,9 @@ export class NumberedBarRenderer extends LineBarRenderer {
this.createLinePreBeatGlyphs();
const hasSpaceAfterStartGlyphs = this.createStartSpacing();
if (this.shouldCreateBarNumber()) {
this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1));
const barNumberGlyph = new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1);
this.barNumberGlyph = barNumberGlyph;
this.addPreBeatGlyph(barNumberGlyph);
} else if (!hasSpaceAfterStartGlyphs) {
this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace));
}
Expand Down
32 changes: 31 additions & 1 deletion packages/alphatab/src/rendering/glyphs/BeatContainerGlyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ export abstract class BeatContainerGlyphBase extends Glyph {
public scaleToWidth(beatWidth: number) {
this.width = beatWidth;
}

/**
* Repositions this beat so its {@link onTimeX} anchor lands at `target`, used for the
* centered full-bar note/rest (see {@link BarLayoutingInfo.isCenteredFullBar}). The default
* shifts the whole container, matching the regular (non-centered) positioning formula.
* {@link BeatContainerGlyph} overrides this to shift only its ink, keeping `x`/`width`
* spanning the full bar so bounds lookups and skyline emission stay correct.
*/
public applyCenterOffset(target: number): void {
this.x = target - this.onTimeX;
}
}

/**
Expand Down Expand Up @@ -214,14 +225,33 @@ export class BeatContainerGlyph extends BeatContainerGlyphBase {
this.preNotes.renderer = this.renderer;
this.preNotes.container = this;
this.preNotes.doLayout();
this.onNotes.x = this.preNotes.x + this.preNotes.width;
// preNotes.width is only final once preNotes.doLayout() has run, so onNotes.x must be
// derived here, not before.
this._layoutOnsetX();
this.onNotes.renderer = this.renderer;
this.onNotes.container = this;
this.onNotes.doLayout();
this.createBeatTies();
this.updateWidth();
}

/**
* Resets `preNotes.x`/`onNotes.x` to their natural (un-centered) baseline.
* Shared by {@link doLayout} and {@link applyCenterOffset} so the latter can be called
* repeatedly (once per `_scaleToForce` pass) without compounding a previous offset.
*/
private _layoutOnsetX(): void {
this.preNotes.x = 0;
this.onNotes.x = this.preNotes.x + this.preNotes.width;
}

public override applyCenterOffset(target: number): void {
this._layoutOnsetX();
const offset = target - this.onTimeX;
this.preNotes.x = offset;
this.onNotes.x = this.preNotes.x + this.preNotes.width;
}

protected createBeatTies() {
let i: number = this.beat.notes.length - 1;
while (i >= 0) {
Expand Down
44 changes: 44 additions & 0 deletions packages/alphatab/src/rendering/glyphs/MultiVoiceContainerGlyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering
import { BeatXPosition } from '@coderline/alphatab/rendering/BeatXPosition';
import type { BeatContainerGlyphBase } from '@coderline/alphatab/rendering/glyphs/BeatContainerGlyph';
import { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph';
import type { LineBarRenderer } from '@coderline/alphatab/rendering/LineBarRenderer';
import { StaffSide } from '@coderline/alphatab/rendering/skyline/BarLocalSkyline';
import type { BarLayoutingInfo } from '@coderline/alphatab/rendering/staves/BarLayoutingInfo';
import type { BarBounds } from '@coderline/alphatab/rendering/utils/BarBounds';
Expand Down Expand Up @@ -60,9 +61,52 @@ export class MultiVoiceContainerGlyph extends Glyph {
this._scaleToForce(force, true);
}

/**
* `true` when every voice/track/staff contributes exactly one beat, of uniform duration,
* spanning the bar's entire duration - i.e. a single full-bar note/rest. Common engraving
* practice centers such notes horizontally within the bar rather than anchoring them right
* after the pre-beat content (Behind Bars, p. 41; see #2464).
*/
private _isCenteredFullBar(): boolean {
// single spring which starts at start and spans the whole bar?
// also ensure we do not have any grace notes
const masterBar = this.renderer.bar.masterBar;
const layoutingInfo = this.renderer.layoutingInfo;
const springs = layoutingInfo.springs;
if (springs.size !== 1 || !springs.has(masterBar.start) || layoutingInfo.allGraceRods.size > 0) {
return false;
}

const spring = springs.get(masterBar.start)!;
return spring.allDurations.size === 1 && spring.longestDuration === masterBar.calculateDuration();
}

/** `emit=false`: positioning-only path; final skyline emission runs later via {@link scaleToWidth}. */
private _scaleToForce(force: number, emit: boolean): void {
this.width = this.renderer.layoutingInfo.calculateVoiceWidth(force);

if (this._isCenteredFullBar()) {
// Sole full-bar beat: keep x=0/width=full-bar so bounds lookups, hit-testing and
// skyline emission still span the whole bar, and shift only the ink via
// applyCenterOffset instead of moving the container itself (which would leave the
// bar's left portion outside this beat's bounds).

// temporary workaround for https://github.com/CoderLine/alphaTab/issues/2780
const barNumberWidth = (this.renderer as LineBarRenderer).barNumberWidth;

const target = (this.width - barNumberWidth) / 2;
for (const beatGlyphs of this.beatGlyphs.values()) {
const soleBeatGlyph = beatGlyphs[0];
soleBeatGlyph.x = 0;
soleBeatGlyph.applyCenterOffset(target);
soleBeatGlyph.scaleToWidth(this.width);
if (emit) {
this._emitBeatContainerSkyline(soleBeatGlyph);
}
}
return;
}

const positions = this.renderer.layoutingInfo.buildOnTimePositions(force);
for (const beatGlyphs of this.beatGlyphs.values()) {
for (let i: number = 0, j: number = beatGlyphs.length; i < j; i++) {
Expand Down
20 changes: 5 additions & 15 deletions packages/alphatab/src/rendering/staves/BarLayoutingInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,11 +432,7 @@ export class BarLayoutingInfo {
* Pair-overlap + last-rod phantom-next-beat for a single band's rod list.
* Called once per band by {@link _calculateSpringConstants}.
*/
private _applyOverlayRodConstraints(
rods: OverlayRod[],
sortedSprings: Spring[],
overlayPadding: number
): void {
private _applyOverlayRodConstraints(rods: OverlayRod[], sortedSprings: Spring[], overlayPadding: number): void {
if (rods.length === 0) {
return;
}
Expand All @@ -445,10 +441,7 @@ export class BarLayoutingInfo {
// anchored in [A.timePosition, B.timePosition) and convert the required gap
// `A.rightExtent + B.leftExtent + padding` to a force `requiredGap / invSum`.
let springIdx = 0;
while (
springIdx < sortedSprings.length &&
sortedSprings[springIdx].timePosition !== rods[0].timePosition
) {
while (springIdx < sortedSprings.length && sortedSprings[springIdx].timePosition !== rods[0].timePosition) {
springIdx++;
}

Expand All @@ -457,10 +450,7 @@ export class BarLayoutingInfo {
const b = rods[r];

let invSum = 0;
while (
springIdx < sortedSprings.length &&
sortedSprings[springIdx].timePosition !== b.timePosition
) {
while (springIdx < sortedSprings.length && sortedSprings[springIdx].timePosition !== b.timePosition) {
invSum += 1 / sortedSprings[springIdx].springConstant;
springIdx++;
}
Expand All @@ -483,8 +473,7 @@ export class BarLayoutingInfo {
const overlayRightRequirement = lastRod.rightExtent + overlayPadding;
const naturalRightBudget = lastSpring.postSpringWidth + this.postBeatSize;
if (overlayRightRequirement > naturalRightBudget) {
const requiredForce =
(overlayRightRequirement - this.postBeatSize) * lastSpring.springConstant;
const requiredForce = (overlayRightRequirement - this.postBeatSize) * lastSpring.springConstant;
this._updateMinStretchForce(requiredForce);
}
}
Expand Down Expand Up @@ -594,6 +583,7 @@ export class BarLayoutingInfo {
if (sortedSprings.length === 0) {
return positions;
}

let springX: number = sortedSprings[0].preSpringWidth;
for (let i: number = 0; i < sortedSprings.length; i++) {
positions.set(sortedSprings[i].timePosition, springX);
Expand Down
Binary file modified packages/alphatab/test-data/musicxml-samples/Binchois.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified packages/alphatab/test-data/musicxml-testsuite/12a-Clefs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified packages/alphatab/test-data/musicxml-testsuite/32a-Notations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified packages/alphatab/test-data/musicxml-testsuite/34b-Colors.png
Binary file modified packages/alphatab/test-data/musicxml-testsuite/34c-Font-Size.png
Binary file modified packages/alphatab/test-data/musicxml-testsuite/46a-Barlines.png
Binary file modified packages/alphatab/test-data/musicxml-testsuite/51d-EmptyTitle.png
Binary file modified packages/alphatab/test-data/musicxml-testsuite/52a-PageLayout.png
Binary file modified packages/alphatab/test-data/musicxml-testsuite/52b-Breaks.png
Binary file modified packages/alphatab/test-data/musicxml3/full-bar-rest.png
Binary file modified packages/alphatab/test-data/visual-tests/special-tracks/slash.png
11 changes: 11 additions & 0 deletions packages/alphatab/test/visualTests/features/SpecialNotes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,15 @@ describe('SpecialNotesTests', () => {
it('beaming-mode', async () => {
await VisualTestHelper.runVisualTest('special-notes/beaming-mode.gp');
});

it('full-bar-notes-rests', async () => {
await VisualTestHelper.runVisualTestTex(
`
C4 {slur s1} | C4 {slur s1} .1 | r.4 | r.1
\\ts(3 8) C4{slur S2}.4 {d} | C4 {slur S2} |
\\ts(4 4) C4{gr} C4.1
`,
'test-data/visual-tests/special-notes/full-bar-notes-rests.png'
);
});
});
Loading