From 3ea1e1cdd63edd924457e81b92ea7fc4e29603aa Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 19:39:53 +0100 Subject: [PATCH 01/14] docs: register reference anchors so the broken anchor check sees them Enum members, settings categories and the alphaTex syntax terms were anchored with plain HTML ids, which Docusaurus does not collect, so the build reported over a hundred broken anchors that resolve in the browser. They are now rendered through Link, which registers the id. The exporter guide and the 1.7 release notes pointed at anchors that do not exist. Broken anchors now fail the build like broken links. --- website/docs/alphatex/syntax.mdx | 11 ++++++----- website/docs/guides/exporter.mdx | 2 +- website/docs/releases/release1_7.mdx | 2 +- website/docusaurus.config.mts | 1 + website/scripts/generate-common.mts | 8 +++++++- website/scripts/generate-typedocs.mts | 2 +- 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/website/docs/alphatex/syntax.mdx b/website/docs/alphatex/syntax.mdx index c0ce6963d..0eb68615d 100644 --- a/website/docs/alphatex/syntax.mdx +++ b/website/docs/alphatex/syntax.mdx @@ -12,6 +12,7 @@ keywords: - fret notation --- +import Link from '@docusaurus/Link'; import { AlphaTexSample } from '@site/src/components/AlphaTexSample'; This page describes the alphaTex source syntax and defines the vocabulary the rest of the reference uses. Every other page links back to the anchors here on first use of a term. Save alphaTex source with the file extension `.atex`. @@ -55,10 +56,10 @@ Examples: ## Argument lists {#argument-lists} - + An **argument list** is the group of values you pass into a metadata tag or a property. Wrap multiple values in parentheses. Omit the parentheses when you pass a single value. - + Each entry inside the list is called an **argument**. The reference tables describe the matching **parameters** — the positional slots that the language accepts for a given construct. An argument can be: @@ -83,7 +84,7 @@ Argument entries are separated by whitespace. Commas are not used. ## Properties {#properties} - + A **property** is a `name arguments` pair inside curly braces. Properties attach to beats, notes, and metadata tags to add effects, visibility flags, and other optional aspects. Property names are case-insensitive. The argument list of a property follows the rules in [Argument lists](#argument-lists). @@ -96,7 +97,7 @@ C4 { b (0 4) v txt "Bend with Vibrato" } ## Metadata tags {#metadata-tags} - + A **metadata tag** is a backslash-prefixed construct of the form `\name arguments {properties}`. The argument list and the property block are both optional; individual tags decide which arguments and properties they accept. Metadata tag names are case-insensitive. @@ -145,7 +146,7 @@ A property attached to a single note, controlling effects specific to that note. Every reference entry lists one or more **signatures**. A signature is one accepted parameter list for the construct — the parameters it names, their types, and their order. When you write a metadata tag or a property, your argument list must match exactly one of its signatures. - + A construct with more than one signature is **overloaded**. `\title`, for example, accepts a single string in one signature and a title-plus-template-plus-alignment triple in another. The reference page for each construct shows every signature it accepts. ## Case sensitivity {#case-sensitivity} diff --git a/website/docs/guides/exporter.mdx b/website/docs/guides/exporter.mdx index b03c7e686..77dc4d7ec 100644 --- a/website/docs/guides/exporter.mdx +++ b/website/docs/guides/exporter.mdx @@ -66,7 +66,7 @@ using(var fs = new System.IO.FileStream(fileName, System.IO.FileMode.Create)) Starting with 1.7.0 alphaTab can also export the data model to its own music notation language [alphaTex](/docs/alphatex/introduction). The exporter supports all features which the importer can also handle. If you find something, please open a feature request. -Via the exporter settings [`settings.exporter`](/docs/reference/settings.mdx#Exporter) the format of the exported code can be adjusted. +Via the exporter settings [`settings.exporter`](/docs/reference/settings.mdx#exporter) the format of the exported code can be adjusted. Especially the `comments` can be quite useful for alphaTex beginners to learn about the syntax. While you can use the standard `export` to export the code into a byte array, you can also use `exportToString` to get the alphaTex code diff --git a/website/docs/releases/release1_7.mdx b/website/docs/releases/release1_7.mdx index b0ef4b92e..51189a915 100644 --- a/website/docs/releases/release1_7.mdx +++ b/website/docs/releases/release1_7.mdx @@ -195,7 +195,7 @@ The details are now documented in the new [language documentation](/docs/alphate Two key changes in the language, compared to 1.6, are: * The documents do not need to be separated with dots into 3 sections anymore. See [Document Structure](/docs/alphatex/document-structure). -* Metadata tags and properties now follow more a function call approach where values are passed as arguments into them. Single values do not need parenthesis but multiple values should be wrapped. [See Parameters](/docs/alphatex/syntax#parameters--argument-lists). +* Metadata tags and properties now follow more a function call approach where values are passed as arguments into them. Single values do not need parenthesis but multiple values should be wrapped. [See Parameters](/docs/alphatex/syntax#argument-lists). #### alphaTex: Language Server and Monaco Integration https://github.com/CoderLine/alphaTab/pull/2379 diff --git a/website/docusaurus.config.mts b/website/docusaurus.config.mts index 221765c6d..eb5436c6b 100644 --- a/website/docusaurus.config.mts +++ b/website/docusaurus.config.mts @@ -208,6 +208,7 @@ async function createConfig(): Promise { organizationName: "CoderLine", projectName: "alphaTab", onBrokenLinks: "throw", + onBrokenAnchors: "throw", future: { v4: { removeLegacyPostBuildHeadAttribute: true, diff --git a/website/scripts/generate-common.mts b/website/scripts/generate-common.mts index 6d11251fd..0e1d5aa0b 100644 --- a/website/scripts/generate-common.mts +++ b/website/scripts/generate-common.mts @@ -84,6 +84,11 @@ export type ReferencePage = { url: string }; + +function categoryAnchor(category: string) { + return category.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); +} + export class ReferenceTableData { public readonly categories = new Map(); @@ -108,6 +113,7 @@ export async function writeReferenceTable( await table.writeLine(`import { CodeBadge } from "@site/src/components/CodeBadge";`); await table.writeLine(`import { Signature } from "@site/src/components/Signature";`); + await table.writeLine(`import Link from "@docusaurus/Link";`); await table.writeLine(); await table.writeLine(``); await table.writeLine(` `); @@ -125,7 +131,7 @@ export async function writeReferenceTable( for (const [category, pages] of categoriesSorted) { - await table.writeLine(` `); + await table.writeLine(` `); const sorted = pages .sort((a, b) => { diff --git a/website/scripts/generate-typedocs.mts b/website/scripts/generate-typedocs.mts index 8ab28906a..8d5fd015e 100644 --- a/website/scripts/generate-typedocs.mts +++ b/website/scripts/generate-typedocs.mts @@ -301,7 +301,7 @@ async function writeEnumMembers( await fileStream.write(` \n`); await fileStream.write( - ` \n` + ` \n` ); const numericValue = context.checker.getConstantValue(member); From 3b182f7e4db3ac1b64cc22901e3f8c1608f78f6b Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 20:10:29 +0100 Subject: [PATCH 02/14] ci: deploy the website only through the publish workflows Publish and Release already build and deploy the website after the web packages, including dry runs. The standalone Website workflow built against whatever the dist-tags served and could deploy a website whose packages were not published yet. --- .github/workflows/website.yml | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 .github/workflows/website.yml diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml deleted file mode 100644 index 7cf02191e..000000000 --- a/.github/workflows/website.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Redeploys the website without publishing packages: docs-only changes, and docs hotfixes on main -# (back-merge those to develop). -name: Website - -on: - workflow_dispatch: - inputs: - channel: - type: choice - description: next (develop → next.alphatab.net) or stable (main → alphatab.net) - options: - - next - - stable - default: next - dry_run: - type: boolean - description: Build the website without deploying it - default: false - -permissions: - contents: read - -jobs: - website: - uses: ./.github/workflows/~publish_website.yml - secrets: - DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} - with: - channel: ${{ inputs.channel }} - npm_tag: ${{ inputs.channel == 'stable' && 'latest' || 'alpha' }} - deploy_branch: ${{ inputs.channel == 'stable' && 'main' || 'develop' }} - dry_run: ${{ inputs.dry_run }} From 0c70994830a7357863f03746b93df5c04dc4e7e7 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 20:10:42 +0100 Subject: [PATCH 03/14] ci: install the website workspaces unconditionally The ui and react packages are part of the monorepo, so the CI and website jobs no longer probe for them before installing and building. --- .github/workflows/build.yml | 26 ++++++-------------------- .github/workflows/~publish_website.yml | 19 +++++-------------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5625a8c76..354be3f9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -180,25 +180,18 @@ jobs: with: node-version: 'lts/*' cache: 'npm' - - id: workspaces - if: steps.dist-cache.outputs.cache-hit != 'true' - run: | - args="" - for ws in packages/alphatab packages/alphatex packages/transpiler packages/tooling packages/lsp packages/monaco packages/webpack packages/vite packages/react; do - if [ -f "$ws/package.json" ]; then args="$args -w $ws"; fi - done - echo "args=$args" >> "$GITHUB_OUTPUT" - if: steps.dist-cache.outputs.cache-hit != 'true' - run: npm ci $WORKSPACES --include-workspace-root - env: - WORKSPACES: ${{ steps.workspaces.outputs.args }} + run: > + npm ci --include-workspace-root + -w packages/alphatab -w packages/alphatex -w packages/transpiler -w packages/tooling + -w packages/lsp -w packages/monaco -w packages/webpack -w packages/vite -w packages/react - if: steps.dist-cache.outputs.cache-hit != 'true' run: npm run build-web - if: steps.dist-cache.outputs.cache-hit != 'true' run: npm run build-language-server - if: steps.dist-cache.outputs.cache-hit != 'true' run: npm run build-monaco - - if: steps.dist-cache.outputs.cache-hit != 'true' && hashFiles('packages/react/package.json') != '' + - if: steps.dist-cache.outputs.cache-hit != 'true' run: npm run build -w packages/react - if: steps.dist-cache.outputs.cache-hit != 'true' run: node scripts/check-published-types.mts @@ -266,14 +259,7 @@ jobs: path: ~/.npm key: npm-website-${{ runner.os }}-${{ hashFiles('package-lock.json') }} restore-keys: npm-website-${{ runner.os }}- - - id: workspaces - run: | - args="-w website" - if [ -f packages/ui/package.json ]; then args="$args -w packages/ui"; fi - echo "args=$args" >> "$GITHUB_OUTPUT" - - run: npm ci $WORKSPACES --include-workspace-root - env: - WORKSPACES: ${{ steps.workspaces.outputs.args }} + - run: npm ci --include-workspace-root -w website -w packages/ui - uses: actions/download-artifact@v8 with: name: web-dist diff --git a/.github/workflows/~publish_website.yml b/.github/workflows/~publish_website.yml index f5368351a..4c1da20ae 100644 --- a/.github/workflows/~publish_website.yml +++ b/.github/workflows/~publish_website.yml @@ -90,21 +90,12 @@ jobs: path: ~/.npm key: npm-website-${{ runner.os }}-${{ hashFiles('package-lock.json') }} restore-keys: npm-website-${{ runner.os }}- - - id: workspaces - run: | - args="-w website" - if [ -f packages/ui/package.json ]; then args="$args -w packages/ui"; fi - # building react from source needs the alphaTab sources and the shared build tooling - if [ -f packages/react/package.json ]; then - args="$args -w packages/react -w packages/alphatab -w packages/transpiler -w packages/tooling" - fi - echo "args=$args" >> "$GITHUB_OUTPUT" - - run: npm ci $WORKSPACES --include-workspace-root - env: - WORKSPACES: ${{ steps.workspaces.outputs.args }} + # building react from source needs the alphaTab sources and the shared build tooling + - run: > + npm ci --include-workspace-root -w website -w packages/ui + -w packages/react -w packages/alphatab -w packages/transpiler -w packages/tooling - - if: hashFiles('packages/react/package.json') != '' - run: | + - run: | npm run generate-typescript npm run build -w packages/react From 5241998cd95ad7db240f838fe4cff11cb631dfc0 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 20:10:50 +0100 Subject: [PATCH 04/14] ci: deploy the website without the enable switch The deploy is live; whether a run deploys is decided by the website and dry run inputs of the publish workflows and by the website environments. --- .github/workflows/~publish_website.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/~publish_website.yml b/.github/workflows/~publish_website.yml index 4c1da20ae..694ac8a51 100644 --- a/.github/workflows/~publish_website.yml +++ b/.github/workflows/~publish_website.yml @@ -141,7 +141,7 @@ jobs: deploy: needs: build - if: ${{ !cancelled() && needs.build.result == 'success' && !inputs.dry_run && vars.WEBSITE_DEPLOY_ENABLED == 'true' }} + if: ${{ !cancelled() && needs.build.result == 'success' && !inputs.dry_run }} runs-on: ubuntu-latest environment: name: website From 845a2101a80490f63da85ce30c1f160b086f2233 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 20:16:20 +0100 Subject: [PATCH 05/14] fix(website): give the playback demos a play button, keep the rest silent The feature demos built on AlphaTabProvider turned the player on but had no control to start it, so pages loaded the synthesizer and SoundFont without a way to play. Demos about rendering, import, export and layout no longer enable the player; the alphaTex live preview and the range selection demo get a shared play/pause button. --- .../src/components/AlphaTabPetaluma/index.tsx | 1 - .../AlphaTexExporterSample/index.tsx | 2 +- .../components/AlphaTexLivePreview/index.tsx | 2 ++ website/src/components/BarRangeDemo/index.tsx | 2 +- .../components/BoundsLookupViewer/index.tsx | 2 +- .../components/BracketModeSwitch/index.tsx | 2 +- .../src/components/BytesLoadDemo/index.tsx | 2 +- .../ChordDiagramLayoutSwitch/index.tsx | 2 +- .../src/components/ColoringExample/index.tsx | 2 +- .../components/CoveragePaletteDemo/index.tsx | 2 +- .../src/components/DemoPlayButton/index.tsx | 29 +++++++++++++++++++ .../DemoPlayButton/styles.module.scss | 5 ++++ .../components/DirectImporterDemo/index.tsx | 2 +- .../FormattingTemplateSample/index.tsx | 2 +- .../src/components/Gp7ExporterDemo/index.tsx | 2 +- .../components/GpVersionSwitcher/index.tsx | 2 +- .../components/HideDynamicsToggle/index.tsx | 2 +- .../components/ImporterKnobsDemo/index.tsx | 2 +- .../src/components/LoadErrorDemo/index.tsx | 2 +- .../src/components/MidiExporterDemo/index.tsx | 2 +- .../components/MultiBarRestToggle/index.tsx | 2 +- .../components/MutateModelExample/index.tsx | 2 +- .../ProgrammaticScoreExample/index.tsx | 2 +- .../components/ResponsiveResizeDemo/index.tsx | 2 +- .../src/components/ScoreJsonDemo/index.tsx | 2 +- .../components/ScoreLoadedInspector/index.tsx | 2 +- .../components/SelectionHandleDemo/index.tsx | 2 ++ .../SelectionHandleDemo/styles.module.scss | 5 +++- .../src/components/SettingsJsonDemo/index.tsx | 1 - .../components/SpacingSlidersDemo/index.tsx | 1 - .../components/SystemSeparatorDemo/index.tsx | 2 +- .../components/ToggleElementsDemo/index.tsx | 2 +- .../components/TrackNamePolicyDemo/index.tsx | 2 +- .../src/components/TrackSwitcher/index.tsx | 2 +- .../src/components/TuningModeSwitch/index.tsx | 1 - .../src/components/UrlLoadSwitcher/index.tsx | 2 +- .../src/components/ZoomSliderDemo/index.tsx | 1 - 37 files changed, 69 insertions(+), 33 deletions(-) create mode 100644 website/src/components/DemoPlayButton/index.tsx create mode 100644 website/src/components/DemoPlayButton/styles.module.scss diff --git a/website/src/components/AlphaTabPetaluma/index.tsx b/website/src/components/AlphaTabPetaluma/index.tsx index 64bdd379b..788e29941 100644 --- a/website/src/components/AlphaTabPetaluma/index.tsx +++ b/website/src/components/AlphaTabPetaluma/index.tsx @@ -35,7 +35,6 @@ export const AlphaTabPetaluma: React.FC = ({ }) => { const tex = typeof children === "string" ? children : undefined; const site = useSiteAlphaTabSettings({ - player: true, settings: { core: { // Plain object form is accepted by settings.fillFromJson at runtime; diff --git a/website/src/components/AlphaTexExporterSample/index.tsx b/website/src/components/AlphaTexExporterSample/index.tsx index cceb37f3d..fa2beb027 100644 --- a/website/src/components/AlphaTexExporterSample/index.tsx +++ b/website/src/components/AlphaTexExporterSample/index.tsx @@ -75,7 +75,7 @@ const AlphaTexExporterInner: FC = () => { }; export const AlphaTexExporterSample: FC = () => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); return ( { spellCheck={false} onChange={(e) => onChange(e.target.value)} /> + {showCode ? {code} : null} diff --git a/website/src/components/BarRangeDemo/index.tsx b/website/src/components/BarRangeDemo/index.tsx index 4bb0b77bf..c7f645a37 100644 --- a/website/src/components/BarRangeDemo/index.tsx +++ b/website/src/components/BarRangeDemo/index.tsx @@ -40,7 +40,7 @@ export const BarRangeDemo: React.FC = ({ () => ({ display: { startBar, barCount } }), [startBar, barCount], ); - const site = useSiteAlphaTabSettings({ player: true, settings: rangeSettings }); + const site = useSiteAlphaTabSettings({ settings: rangeSettings }); const useTex = !file; const texContent = useTex ? tex ?? DEFAULT_TEX : undefined; diff --git a/website/src/components/BoundsLookupViewer/index.tsx b/website/src/components/BoundsLookupViewer/index.tsx index 9128f9666..0b50dc694 100644 --- a/website/src/components/BoundsLookupViewer/index.tsx +++ b/website/src/components/BoundsLookupViewer/index.tsx @@ -197,7 +197,7 @@ export const BoundsLookupViewer: React.FC = ({ }; const tex = typeof children === "string" ? children : undefined; - const site = useSiteAlphaTabSettings({ player: true, settings: NOTE_BOUNDS_SETTINGS }); + const site = useSiteAlphaTabSettings({ settings: NOTE_BOUNDS_SETTINGS }); return ( <> diff --git a/website/src/components/BracketModeSwitch/index.tsx b/website/src/components/BracketModeSwitch/index.tsx index 347dc9b26..17a537456 100644 --- a/website/src/components/BracketModeSwitch/index.tsx +++ b/website/src/components/BracketModeSwitch/index.tsx @@ -37,7 +37,7 @@ export const BracketModeSwitch: React.FC = ({ showCode = true, }) => { const [mode, setMode] = useState("GroupStaves"); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `api.scoreLoaded.on((score) => { score.stylesheet.bracketExtendMode = diff --git a/website/src/components/BytesLoadDemo/index.tsx b/website/src/components/BytesLoadDemo/index.tsx index 3b70fb804..fe3ea9567 100644 --- a/website/src/components/BytesLoadDemo/index.tsx +++ b/website/src/components/BytesLoadDemo/index.tsx @@ -125,7 +125,7 @@ export const BytesLoadDemo: React.FC = ({ }) => { const [byteCount, setByteCount] = useState(null); const [format, setFormat] = useState(""); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `const bytes = new Uint8Array( await fetch('/scores/song.gp').then((r) => r.arrayBuffer()) diff --git a/website/src/components/ChordDiagramLayoutSwitch/index.tsx b/website/src/components/ChordDiagramLayoutSwitch/index.tsx index e32d56347..a2884d2cb 100644 --- a/website/src/components/ChordDiagramLayoutSwitch/index.tsx +++ b/website/src/components/ChordDiagramLayoutSwitch/index.tsx @@ -37,7 +37,7 @@ export const ChordDiagramLayoutSwitch: React.FC< ChordDiagramLayoutSwitchProps > = ({ showCode = true }) => { const [placement, setPlacement] = useState("Top"); - const site = useSiteAlphaTabSettings({ player: true, settings: BAR_COUNT_SETTINGS }); + const site = useSiteAlphaTabSettings({ settings: BAR_COUNT_SETTINGS }); const code = `api.scoreLoaded.on((score) => { score.stylesheet.globalDisplayChordDiagramsOnTop = ${placement === "Top"}; diff --git a/website/src/components/ColoringExample/index.tsx b/website/src/components/ColoringExample/index.tsx index 73049d293..fa0126f0c 100644 --- a/website/src/components/ColoringExample/index.tsx +++ b/website/src/components/ColoringExample/index.tsx @@ -85,7 +85,7 @@ function ApplyColors() { export const ColoringExample: React.FC = ({ showCode = true, }) => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const codeLines = ` const api = new alphaTab.AlphaTabApi(...); diff --git a/website/src/components/CoveragePaletteDemo/index.tsx b/website/src/components/CoveragePaletteDemo/index.tsx index 1afc2d165..d2e75d221 100644 --- a/website/src/components/CoveragePaletteDemo/index.tsx +++ b/website/src/components/CoveragePaletteDemo/index.tsx @@ -38,7 +38,7 @@ export const CoveragePaletteDemo: React.FC = ({ () => ({ display: { resources: palette } }), [palette], ); - const site = useSiteAlphaTabSettings({ player: true, settings: paletteSettings }); + const site = useSiteAlphaTabSettings({ settings: paletteSettings }); const set = (key: Field, value: string) => { setPalette((p) => ({ ...p, [key]: value })); diff --git a/website/src/components/DemoPlayButton/index.tsx b/website/src/components/DemoPlayButton/index.tsx new file mode 100644 index 000000000..c3437eecf --- /dev/null +++ b/website/src/components/DemoPlayButton/index.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import * as alphaTab from "@coderline/alphatab"; +import { useAlphaTabApi, useAlphaTabEventValue } from "@coderline/alphatab-react"; +import { IconPause, IconPlay } from "@coderline/alphatab-ui"; +import styles from "./styles.module.scss"; + +const isPlayingState = (state: alphaTab.synth.PlayerState) => state === alphaTab.synth.PlayerState.Playing; + +/** Play/pause for the score of the surrounding `AlphaTabProvider`; disabled until the player is ready. */ +export const DemoPlayButton: React.FC<{ className?: string }> = ({ className }) => { + const api = useAlphaTabApi(); + const ready = useAlphaTabEventValue("playerReady", () => true, (a) => a.isReadyForPlayback) ?? false; + const isPlaying = + useAlphaTabEventValue("playerStateChanged", (e) => isPlayingState(e.state), (a) => isPlayingState(a.playerState)) ?? + false; + + return ( + + ); +}; diff --git a/website/src/components/DemoPlayButton/styles.module.scss b/website/src/components/DemoPlayButton/styles.module.scss new file mode 100644 index 000000000..27369f388 --- /dev/null +++ b/website/src/components/DemoPlayButton/styles.module.scss @@ -0,0 +1,5 @@ +.play { + align-self: flex-start; + display: inline-flex; + align-items: center; +} diff --git a/website/src/components/DirectImporterDemo/index.tsx b/website/src/components/DirectImporterDemo/index.tsx index bcb3b5486..b9ad97b00 100644 --- a/website/src/components/DirectImporterDemo/index.tsx +++ b/website/src/components/DirectImporterDemo/index.tsx @@ -79,7 +79,7 @@ export const DirectImporterDemo: React.FC = ({ showCode = true, }) => { const [info, setInfo] = useState(null); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `import * as alphaTab from '@coderline/alphatab'; diff --git a/website/src/components/FormattingTemplateSample/index.tsx b/website/src/components/FormattingTemplateSample/index.tsx index 56607bf1f..5fe9887d7 100644 --- a/website/src/components/FormattingTemplateSample/index.tsx +++ b/website/src/components/FormattingTemplateSample/index.tsx @@ -58,7 +58,7 @@ const FormattingTemplateInner: React.FC = () => { }; export const FormattingTemplateSample: React.FC = () => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const codeLines = ` function adjustStyle(score: alphaTab.model.Score) { diff --git a/website/src/components/Gp7ExporterDemo/index.tsx b/website/src/components/Gp7ExporterDemo/index.tsx index 8bbec8ad3..f2b246edb 100644 --- a/website/src/components/Gp7ExporterDemo/index.tsx +++ b/website/src/components/Gp7ExporterDemo/index.tsx @@ -105,7 +105,7 @@ const Gp7ExporterControls: React.FC = () => { export const Gp7ExporterDemo: React.FC = ({ showCode = true, }) => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); return (
= ({ showCode = true, }) => { const [active, setActive] = useState(INITIAL_VARIANT); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `const api = new alphaTab.AlphaTabApi(container, { core: { file: '/scores/song.gp' }, // GP7/8 ZIP archive diff --git a/website/src/components/HideDynamicsToggle/index.tsx b/website/src/components/HideDynamicsToggle/index.tsx index 9b5ce0261..383d56b8f 100644 --- a/website/src/components/HideDynamicsToggle/index.tsx +++ b/website/src/components/HideDynamicsToggle/index.tsx @@ -31,7 +31,7 @@ export const HideDynamicsToggle: React.FC = ({ showCode = true, }) => { const [hidden, setHidden] = useState(false); - const site = useSiteAlphaTabSettings({ player: true, settings: BAR_COUNT_SETTINGS }); + const site = useSiteAlphaTabSettings({ settings: BAR_COUNT_SETTINGS }); const code = `api.scoreLoaded.on((score) => { score.stylesheet.hideDynamics = ${hidden}; diff --git a/website/src/components/ImporterKnobsDemo/index.tsx b/website/src/components/ImporterKnobsDemo/index.tsx index dd27ccacb..ecba163aa 100644 --- a/website/src/components/ImporterKnobsDemo/index.tsx +++ b/website/src/components/ImporterKnobsDemo/index.tsx @@ -123,7 +123,7 @@ export const ImporterKnobsDemo: React.FC = ({ }) => { const [beatTextAsLyrics, setBeatTextAsLyrics] = useState(false); const [encoding, setEncoding] = useState("windows-1252"); - const site = useSiteAlphaTabSettings({ player: true, settings: IMPORTER_SETTINGS }); + const site = useSiteAlphaTabSettings({ settings: IMPORTER_SETTINGS }); const code = `const api = new alphaTab.AlphaTabApi(container, { core: { file: '/scores/oldfile.gp5' }, diff --git a/website/src/components/LoadErrorDemo/index.tsx b/website/src/components/LoadErrorDemo/index.tsx index bd05b9871..c1ea6eca6 100644 --- a/website/src/components/LoadErrorDemo/index.tsx +++ b/website/src/components/LoadErrorDemo/index.tsx @@ -164,7 +164,7 @@ export const LoadErrorDemo: React.FC = ({ }) => { const [record, setRecord] = useState(null); const [success, setSuccess] = useState(null); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `api.error.on((error) => { if (error instanceof alphaTab.importer.UnsupportedFormatError) { diff --git a/website/src/components/MidiExporterDemo/index.tsx b/website/src/components/MidiExporterDemo/index.tsx index 34c6ca968..0802a33f2 100644 --- a/website/src/components/MidiExporterDemo/index.tsx +++ b/website/src/components/MidiExporterDemo/index.tsx @@ -133,7 +133,7 @@ const MidiExporterControls: React.FC = () => { export const MidiExporterDemo: React.FC = ({ showCode = true, }) => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); return (
= ({ showCode = true, }) => { const [enabled, setEnabled] = useState(true); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `api.scoreLoaded.on((score) => { score.stylesheet.multiTrackMultiBarRest = ${enabled}; diff --git a/website/src/components/MutateModelExample/index.tsx b/website/src/components/MutateModelExample/index.tsx index df47f15c1..5a63ab532 100644 --- a/website/src/components/MutateModelExample/index.tsx +++ b/website/src/components/MutateModelExample/index.tsx @@ -152,7 +152,7 @@ const MutateModelInner: React.FC = () => { }; export const MutateModelExample: React.FC = () => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); return (
{ export const ProgrammaticScoreExample: React.FC = ({ showCode = true, }) => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); return (
= ({ const [width, setWidth] = useState(null); const [dragging, setDragging] = useState(false); const [displayWidth, setDisplayWidth] = useState(0); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); useEffect(() => { if (!dragging) { return; } diff --git a/website/src/components/ScoreJsonDemo/index.tsx b/website/src/components/ScoreJsonDemo/index.tsx index 98d1d0e81..6e3d1d0a0 100644 --- a/website/src/components/ScoreJsonDemo/index.tsx +++ b/website/src/components/ScoreJsonDemo/index.tsx @@ -162,7 +162,7 @@ const ScoreJsonControls: React.FC = () => { export const ScoreJsonDemo: React.FC = ({ showCode = true, }) => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); return (
{ export const ScoreLoadedInspector: React.FC = ({ showCode = true, }) => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `const api = new alphaTab.AlphaTabApi(container, { core: { file: '/scores/song.gp5' }, }); diff --git a/website/src/components/SelectionHandleDemo/index.tsx b/website/src/components/SelectionHandleDemo/index.tsx index 371a1a1af..06ce7e1b7 100644 --- a/website/src/components/SelectionHandleDemo/index.tsx +++ b/website/src/components/SelectionHandleDemo/index.tsx @@ -11,6 +11,7 @@ import { } from "@coderline/alphatab-react"; import { SelectionHandles } from "@coderline/alphatab-ui"; import { useSiteAlphaTabSettings } from "@site/src/alphatab-settings"; +import { DemoPlayButton } from "@site/src/components/DemoPlayButton"; import styles from "./styles.module.scss"; export interface SelectionHandleDemoProps { @@ -88,6 +89,7 @@ export const SelectionHandleDemo: React.FC = ({ settings={site.settings} > {showEventLog && } + } /> {showEventLog && ( diff --git a/website/src/components/SelectionHandleDemo/styles.module.scss b/website/src/components/SelectionHandleDemo/styles.module.scss index 669b5f00a..0148ee9d0 100644 --- a/website/src/components/SelectionHandleDemo/styles.module.scss +++ b/website/src/components/SelectionHandleDemo/styles.module.scss @@ -59,4 +59,7 @@ line-height: 1.5; white-space: pre; font-variant-numeric: tabular-nums; -} \ No newline at end of file +} +.play { + margin-bottom: 0.5rem; +} diff --git a/website/src/components/SettingsJsonDemo/index.tsx b/website/src/components/SettingsJsonDemo/index.tsx index 734d73b05..05b4ed9b5 100644 --- a/website/src/components/SettingsJsonDemo/index.tsx +++ b/website/src/components/SettingsJsonDemo/index.tsx @@ -245,7 +245,6 @@ export const SettingsJsonDemo: React.FC = ({ showCode = true, }) => { const site = useSiteAlphaTabSettings({ - player: true, settings: { display: { scale: 1.0 } }, }); return ( diff --git a/website/src/components/SpacingSlidersDemo/index.tsx b/website/src/components/SpacingSlidersDemo/index.tsx index 617d6441e..04b8c1747 100644 --- a/website/src/components/SpacingSlidersDemo/index.tsx +++ b/website/src/components/SpacingSlidersDemo/index.tsx @@ -24,7 +24,6 @@ export const SpacingSlidersDemo: React.FC = ({ const [spacingRatio, setSpacingRatio] = useState(1.5); const [lastSystemFillThreshold, setLastSystemFillThreshold] = useState(0.5); const site = useSiteAlphaTabSettings({ - player: true, settings: { display: { stretchForce, diff --git a/website/src/components/SystemSeparatorDemo/index.tsx b/website/src/components/SystemSeparatorDemo/index.tsx index 539c577ca..64002f564 100644 --- a/website/src/components/SystemSeparatorDemo/index.tsx +++ b/website/src/components/SystemSeparatorDemo/index.tsx @@ -41,7 +41,7 @@ export const SystemSeparatorDemo: React.FC = ({ showCode = true, }) => { const [enabled, setEnabled] = useState(true); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `api.scoreLoaded.on((score) => { score.stylesheet.useSystemSignSeparator = ${enabled}; diff --git a/website/src/components/ToggleElementsDemo/index.tsx b/website/src/components/ToggleElementsDemo/index.tsx index 8d2c33d70..766ca8cf4 100644 --- a/website/src/components/ToggleElementsDemo/index.tsx +++ b/website/src/components/ToggleElementsDemo/index.tsx @@ -76,7 +76,7 @@ export const ToggleElementsDemo: React.FC = ({ EffectPalmMute: true, }); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const flip = (key: ElementKey) => { setVisibility((v) => ({ ...v, [key]: !v[key] })); diff --git a/website/src/components/TrackNamePolicyDemo/index.tsx b/website/src/components/TrackNamePolicyDemo/index.tsx index 51aebe278..b0a2f4468 100644 --- a/website/src/components/TrackNamePolicyDemo/index.tsx +++ b/website/src/components/TrackNamePolicyDemo/index.tsx @@ -65,7 +65,7 @@ export const TrackNamePolicyDemo: React.FC = ({ const [mode, setMode] = useState("FullName"); const [orientation, setOrientation] = useState("Horizontal"); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `api.scoreLoaded.on((score) => { const s = score.stylesheet; diff --git a/website/src/components/TrackSwitcher/index.tsx b/website/src/components/TrackSwitcher/index.tsx index d34226a47..d7d877290 100644 --- a/website/src/components/TrackSwitcher/index.tsx +++ b/website/src/components/TrackSwitcher/index.tsx @@ -129,7 +129,7 @@ const Inner: React.FC = () => { export const TrackSwitcher: React.FC = ({ showCode = true, }) => { - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `const api = new alphaTab.AlphaTabApi(container, { core: { file: '/scores/song.gp5', diff --git a/website/src/components/TuningModeSwitch/index.tsx b/website/src/components/TuningModeSwitch/index.tsx index 26027bcfa..b1539b8de 100644 --- a/website/src/components/TuningModeSwitch/index.tsx +++ b/website/src/components/TuningModeSwitch/index.tsx @@ -49,7 +49,6 @@ export const TuningModeSwitch: React.FC = ({ const [mode, setMode] = useState("Score"); const [hidden, setHidden] = useState(false); const site = useSiteAlphaTabSettings({ - player: true, settings: { display: { barCount: 12 } }, }); diff --git a/website/src/components/UrlLoadSwitcher/index.tsx b/website/src/components/UrlLoadSwitcher/index.tsx index c6f89e228..fea401d35 100644 --- a/website/src/components/UrlLoadSwitcher/index.tsx +++ b/website/src/components/UrlLoadSwitcher/index.tsx @@ -65,7 +65,7 @@ export const UrlLoadSwitcher: React.FC = ({ showCode = true, }) => { const [currentUrl, setCurrentUrl] = useState(CHORDS_URL); - const site = useSiteAlphaTabSettings({ player: true }); + const site = useSiteAlphaTabSettings(); const code = `const api = new alphaTab.AlphaTabApi(container, { core: { file: '/scores/chords.gp5' }, diff --git a/website/src/components/ZoomSliderDemo/index.tsx b/website/src/components/ZoomSliderDemo/index.tsx index 8686feec6..ab361c309 100644 --- a/website/src/components/ZoomSliderDemo/index.tsx +++ b/website/src/components/ZoomSliderDemo/index.tsx @@ -23,7 +23,6 @@ export const ZoomSliderDemo: React.FC = ({ const [scale, setScale] = useState(1.3); const [pendingScale, setPendingScale] = useState(1.3); const site = useSiteAlphaTabSettings({ - player: true, settings: { display: { scale } }, }); From 3c28d574f24071d1ad2a2935bcb6176c09783a52 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 21:20:03 +0100 Subject: [PATCH 06/14] docs(releases): cover the performance, website, hardening and late fixes in the 1.9.0 notes --- website/docs/releases/release1_9.mdx | 142 ++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 3 deletions(-) diff --git a/website/docs/releases/release1_9.mdx b/website/docs/releases/release1_9.mdx index f14f8f015..31948fcf2 100644 --- a/website/docs/releases/release1_9.mdx +++ b/website/docs/releases/release1_9.mdx @@ -7,7 +7,7 @@ import { AlphaTexSample } from '@site/src/components/AlphaTexSample'; ## 1.9.0 -This release gives the alphaTab attribution a smaller, quieter place in the score, reworks note spacing and the placement of annotations, adds per-staff control over clefs, key and time signatures and bar numbers, plays repeats and jumps in the right order, imports MusicXML files more faithfully, and loads alphaTab's core once for the page, its workers and its Audio Worklet with webpack, Rspack and Vite. The [upgrade notes](#upgrade-notes) at the end list the visual changes, changed defaults and deprecations. +This release gives the alphaTab attribution a smaller, quieter place in the score, renders and resizes in about half the time and memory, reworks note spacing and the placement of annotations, adds per-staff control over clefs, key and time signatures and bar numbers, plays repeats and jumps in the right order, imports MusicXML files more faithfully, and loads alphaTab's core once for the page, its workers and its Audio Worklet with webpack, Rspack, Vite and Turbopack. Before the release we reviewed all of these changes again and fixed what the [review](#release-hardening) found. The website is rebuilt and now lives in the alphaTab repository. The [upgrade notes](#upgrade-notes) at the end list the visual changes, changed defaults and deprecations. ### Attribution @@ -30,6 +30,39 @@ The new [Attribution](/docs/attribution) page explains [why we ask for credit](/ A future release will move from MPL-2.0 to an open-source license with an attribution clause. The choice is not settled yet; [CPAL-1.0](https://opensource.org/license/cpal_1-0) is the best fit we see so far. alphaTab stays open source and free, also for commercial use, and releases under MPL-2.0, including this one, stay under MPL-2.0. See [what changes](/docs/attribution#license-cpal). +### Performance + +#### render: faster layout, and resizes that match a fresh render +https://github.com/CoderLine/alphaTab/pull/2997 +https://github.com/CoderLine/alphaTab/pull/2751 + +The page and parchment layouts measure each bar once, decide the system breaks before they fill the systems, and reuse the systems on a resize. One rule decides which bars fit a system, for the first render, resizes and partial updates alike. A resize now gives the same result as a fresh render at that width: before, 40 % of our test cases differed, with forced line breaks one bar early, bar widths off by up to 0.5 px and shifted system heights. + +Compared with 1.8.4, the first render of a real song takes 28–36 % less time, a resize 13–39 % less, and both allocate about half the memory. On a dense score (216 bars, 20 staves), the rework alone brings: + +| | before the rework | 1.9.0 | change | +|---|---|---|---| +| First render (layout and paint, SVG in Node.js) | 200 ms | 105 ms | −47 % | +| Resize 1300 → 800 px / 800 → 1300 px | 63 / 55 ms | 24 / 21 ms | −63 % / −60 % | +| Memory allocated per first render | 233 MB | 114 MB | −51 % | +| Memory allocated per resize | 58 / 48 MB | 19 / 12 MB | −67 % / −74 % | +| Heap kept by a finished renderer | 152 MB | 53 MB | −65 % | +| Browser renderer memory after scrolling through the score | 410 MB | 256 MB | −38 % | +| Worker heap in the browser | 81–91 MB | 40–44 MB | about −50 % | +| Time until the visible part is ready, load / resize | 777 / 243 ms | 541 / 108 ms | −30 % / −56 % | + +Full demo and all options: [Responsive resize](/docs/features/render/responsive-resize). + +#### browser: less work between the page, the worker and the player +https://github.com/CoderLine/alphaTab/pull/2997 + +* The score goes to the render worker as one transferred MessagePack buffer instead of a structured-cloned object tree, and the bounds lookup comes back as a transferred `Float64Array`. +* With the render worker, resizes are coalesced while one is in progress: during a window drag, intermediate widths raise no render events (see [`IScoreRenderer.resizeRender`](/docs/reference/types/rendering/iscorerenderer/#resizerender)). +* Parts of the score far out of view release their DOM and render again when they scroll back into view. Before, the whole rendered sheet stayed in memory after scrolling through it once. Beat highlights survive this re-render. +* The MIDI is generated once per score load and player change instead of up to four times, and with a worker the score is sent for rendering before its MIDI is generated. +* The Audio Worklet reports the played position every 10 ms instead of every 128-frame render quantum, which saves messages between the audio thread, the worker and the page. [`player.positionUpdateIntervalInMilliseconds`](/docs/reference/settings/player/positionupdateintervalinmilliseconds) sets the interval; `0` restores the previous behavior. +* [`destroy()`](/docs/reference/api/destroy) releases the loaded score. + ### Music Notation #### render: power-law note spacing and last-system justification @@ -196,6 +229,11 @@ Thanks to [@roscopeeco](https://github.com/roscopeeco) for contributing this fea * render: ties and slurs that continue on a new system start after the clef, key signature and time signature instead of at the left edge of the staff ([#2800](https://github.com/CoderLine/alphaTab/pull/2800), thanks [@GiovanniSlabs](https://github.com/GiovanniSlabs) for contributing this fix) * render: tab staves with slashed beats render stems, beams and dots in place when [`notation.rhythmMode`](/docs/reference/settings/notation/rhythmmode) shows tab rhythm, instead of failing or drawing them at the top of the staff ([#2916](https://github.com/CoderLine/alphaTab/pull/2916), thanks [@zyk-mjzs](https://github.com/zyk-mjzs)) * render: the minimum bar width is computed correctly, and the new engraving settings [`beatContentPadding`](/docs/reference/types/engravingsettings/#beatcontentpadding) and [`barlineContentPadding`](/docs/reference/types/engravingsettings/#barlinecontentpadding) keep accidentals, grace notes and flags clear of the next beat and the barline; a bar that must be squeezed below its minimum logs a warning ([#2943](https://github.com/CoderLine/alphaTab/pull/2943), see [Engraving clearances](/docs/features/render/effect-placement#clearances)) +* render: numbered notation repeats `0` for rests longer than a quarter (`0 0`, `0 0 0 0`) instead of drawing the dashes of held notes ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* render: [voice colors](/docs/reference/types/model/voicestyle) reach stems, beams, flags, tuplets, effects, slur continuations and tab bends on several notes. A `null` entry in a style resets score elements, track names and the tuning in the header to their own defaults, and the system separator is drawn in the main glyph color by default ([#2997](https://github.com/CoderLine/alphaTab/pull/2997), see [Per-element colors](/docs/features/render/per-element-colors)) +* render: the bend labels of several bent notes on one beat are merged and stacked instead of drawn over each other ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* render: the triplet feel indication is drawn once per bar instead of once per voice and staff, and the capo once per staff ([#2992](https://github.com/CoderLine/alphaTab/pull/2992), thanks [@parvanitis15](https://github.com/parvanitis15)) +* render: dead-slapped beats of whole duration or longer, and beams made only of dead slaps, render instead of failing with `Cannot read properties of null (reading 'beat')` ([#2993](https://github.com/CoderLine/alphaTab/pull/2993), thanks [@yezhenbiao](https://github.com/yezhenbiao)) ### Player @@ -283,6 +321,9 @@ Thanks to [@AvaTheArchitect](https://github.com/AvaTheArchitect) for the discuss * player: a [`customScrollHandler`](/docs/reference/api/customscrollhandler) is called also when [`player.scrollMode`](/docs/reference/settings/player/scrollmode) is `Off` ([2889cac](https://github.com/CoderLine/alphaTab/commit/2889cac4e359a41b6f24a82034e6f6ba4f9f4c4f)) * player: changing [`player.enableAnimatedBeatCursor`](/docs/reference/settings/player/enableanimatedbeatcursor) at runtime switches the cursor ([9a23e14](https://github.com/CoderLine/alphaTab/commit/9a23e14da4dc346c709927a9fe85d3d5b769826a)) * integration: a mouse press that ends outside the score, or is lost through a window switch or an Android touch cancel, ends with one mouse-up event; before, the press stayed active, extended the selection on hover and turned off auto-scrolling ([#2951](https://github.com/CoderLine/alphaTab/pull/2951), thanks [@kulek1](https://github.com/kulek1)) +* player: with a [backing track](/docs/features/play-sync/backing-track-audio) or [external media](/docs/features/play-sync/video-sync), the playback position no longer falls further behind the audio over the song at tempos where a bar does not last a whole number of milliseconds, such as 148 BPM. Sync point times and beat timers keep their decimals ([#2960](https://github.com/CoderLine/alphaTab/pull/2960), thanks [@bj0rge](https://github.com/bj0rge)) +* player: fade in, fade out and volume swell ramp the expression controller (CC 11) instead of the channel volume (CC 7), so a fade scales the volume in effect. After a fade out or volume swell the track plays again with the voice's next note; before, it stayed silent until the end and [`changeTrackVolume`](/docs/reference/api/changetrackvolume) could not bring it back ([#2995](https://github.com/CoderLine/alphaTab/pull/2995), thanks [@yoyecros](https://github.com/yoyecros)) +* player: a local `initialAttenuation` in a SoundFont zone replaces the global value instead of adding to it, as the SoundFont 2.04 specification says, so affected presets are no longer too quiet ([#2994](https://github.com/CoderLine/alphaTab/pull/2994), thanks [@fanjink](https://github.com/fanjink)) ### alphaTex Extensions @@ -310,6 +351,8 @@ Full demo and all options: [Staff element display](/docs/features/render/staff-e * alphaTex: the note property [`string`](/docs/alphatex/note-properties#string) shows a string number on pitched notes, as in `a4{string 1}` ([#2913](https://github.com/CoderLine/alphaTab/pull/2913), thanks [@magmasystems](https://github.com/magmasystems)) * alphaTex: harmonic values can be fractional, as in `3.3{ah 2.4}`, so exported files with such values load again ([#2941](https://github.com/CoderLine/alphaTab/pull/2941), thanks [@stewacide](https://github.com/stewacide)) * alphaTex: the exporter writes [`acc`](/docs/alphatex/note-properties#acc) only where the accidental changes the spelling ([#2931](https://github.com/CoderLine/alphaTab/pull/2931), thanks [@gabbe](https://github.com/gabbe)) +* alphaTex: the millisecond offset of [`\sync`](/docs/alphatex/bar-metadata#sync) can have decimals, also in the form without parentheses: `\sync 1 0 1621.62` no longer splits into a sync point at 1621 ms and a note `62` ([#2960](https://github.com/CoderLine/alphaTab/pull/2960)) +* alphaTex: an export and re-import keeps empty Guitar Pro voices, their dynamics and the strings of Guitar Pro 7 and 8 drum notes ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) * alphaTex: `basicEditorLspIntegration` of `@coderline/alphatab-monaco` answers only for its editor's model, so several editors can each use a language server, and returns a disposable; `diagnosticsDelay` and `completionRange` tune diagnostics and completions (see [Monaco](/docs/alphatex/monaco#setup-the-monaco-lsp-integration)) * alphaTex: `@coderline/alphatab-language-server` and `@coderline/alphatab-monaco` bundle and run again; the language server has separate entry points for browsers and Node.js ([#2830](https://github.com/CoderLine/alphaTab/pull/2830); see [Language server](/docs/alphatex/lsp), [Monaco](/docs/alphatex/monaco) and the [package changes](#package-changes)) @@ -339,6 +382,7 @@ Full list of supported elements: [MusicXML feature table](/docs/formats/musicxml * musicxml: harmonics written as base, touching and sounding notes, as Guitar Pro 8 exports them, become one harmonic note; in alphaTex, artificial and other harmonics on pitched notes such as `C4{ah 12}` sound and render ([#2947](https://github.com/CoderLine/alphaTab/pull/2947), thanks [@parvanitis15](https://github.com/parvanitis15)) * musicxml: notes with an `` reference are treated as percussion only when that instrument is unpitched or the staff is a percussion staff ([#2777](https://github.com/CoderLine/alphaTab/pull/2777), thanks [@ldupoueyUB](https://github.com/ldupoueyUB)) * musicxml: high voice numbers are packed into consecutive voices per staff ([#2831](https://github.com/CoderLine/alphaTab/pull/2831)) +* musicxml: a dynamic is shown at the next note of its staff (and voice, if given) instead of the next beat in the file, so it no longer appears on other staves and parts, and no `f` is drawn where the file writes none. Dynamics attached to notes (``) are shown too. For playback a dynamic applies to all staves of its part, as in MuseScore and Dorico; of two dynamics at the same position, the staff's own wins. The new [`Beat.showDynamics`](/docs/reference/types/model/beat/#showdynamics) carries this; other importers leave it unset and render as before ([#2996](https://github.com/CoderLine/alphaTab/pull/2996), thanks [@weblings](https://github.com/weblings)) * musicxml: the `number` attribute of each measure sets custom bar numbers and texts, and implicit measures become pick-up bars ([#2907](https://github.com/CoderLine/alphaTab/pull/2907); see [Bar numbers](/docs/features/render/bar-numbers#file-import)) ### Guitar Pro Extensions @@ -374,28 +418,36 @@ Thanks to [@chenkigba](https://github.com/chenkigba) for contributing this chang * importer/exporter: the tuning display mode of Guitar Pro 7 and 8 files is read and written ([#2742](https://github.com/CoderLine/alphaTab/pull/2742), thanks [@kulek1](https://github.com/kulek1); see [Tuning display](/docs/features/render/tuning-display#tuning-on-staff)) * importer/exporter: the drum tablature lines of Guitar Pro 7 and 8 files are kept on import and export ([#2591](https://github.com/CoderLine/alphaTab/pull/2591), thanks [@leocaseiro](https://github.com/leocaseiro); see [Drum tablature](/docs/features/render/drum-tabs#drum-tablature)) +* importer: a malformed GPX file whose data lies before its file entry no longer makes the import loop forever until it runs out of memory. Invalid sector pointers, truncated entries and file sizes outside the container fail with an [`UnsupportedFormatError`](/docs/reference/types/importer/unsupportedformaterror), and the decompressed size respects [`importer.maxDecodingBufferSize`](/docs/reference/settings/importer/maxdecodingbuffersize) ([#2991](https://github.com/CoderLine/alphaTab/pull/2991), thanks [@CaliLuke](https://github.com/CaliLuke)) +* importer: Guitar Pro 3–5 drum grace notes are read as percussion notes, the note duration percentage is read correctly (several files had broken values), and notes without a fingering get "unknown finger" ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* exporter: beat timers are written to Guitar Pro 7 files as whole milliseconds, as the format stores them ([#2960](https://github.com/CoderLine/alphaTab/pull/2960)) ### Bundlers and Frameworks #### bundlers: one alphaTab core for the page, the workers and the worklet +https://github.com/CoderLine/alphaTab/pull/2998 With webpack, Rspack and Vite, the page, the Web Workers and the Audio Worklet load alphaTab's core from one shared chunk instead of a copy each, so the browser downloads and compiles about 1.2 MiB (minified) once instead of up to three times. alphaTab starts its worker with the standard `new Worker(new URL('./alphaTab.worker.mjs', import.meta.url), { type: 'module' })` and its worklet from `alphaTab.worklet.mjs`, both importing `alphaTab.core.mjs`. The new page [Installing in web projects](/docs/getting-started/installation-web-projects) lists the setup for every bundler and framework with the number of core copies each one loads, and links to a verified sample project for each. New pages cover [other bundlers](/docs/getting-started/installation-other-bundlers), [React and Next.js](/docs/getting-started/installation-react), [Vue and Nuxt](/docs/getting-started/installation-vue), [Angular](/docs/getting-started/installation-angular), [Svelte and SvelteKit](/docs/getting-started/installation-svelte) and [Solid](/docs/getting-started/installation-solid). #### webpack: Rspack and Rsbuild support +https://github.com/CoderLine/alphaTab/pull/2998 `@coderline/alphatab-webpack` works with webpack 5 (5.90 or later), Rspack 1.7 and 2, and Rsbuild through `tools.rspack`. It bundles alphaTab's worker and worklet as entries and moves the core into an `alphaTabCore` split chunk. For that it sets `optimization.usedExports` to `'global'` in production builds when you did not set it. See [Installation (webpack, Rspack, Rsbuild)](/docs/getting-started/installation-webpack). #### vite: worker and worklet as chunks of the main build +https://github.com/CoderLine/alphaTab/pull/2998 `@coderline/alphatab-vite` emits alphaTab's worker and worklet as chunks of the main build, sharing the core chunk with the page, and supports Vite 6, 7 and 8. The URLs are written relative to the chunk that references them, so a relative `base`, `renderBuiltUrl`, SvelteKit and Nuxt 4 work. In the dev server, alphaTab is excluded from dependency pre-bundling. See [Installation (Vite)](/docs/getting-started/installation-vite). #### integration: entries for bundlers without a plugin +https://github.com/CoderLine/alphaTab/pull/2998 The new exports `@coderline/alphatab/core`, `@coderline/alphatab/worker` and `@coderline/alphatab/worklet` let your app own alphaTab's worker and worklet entries. `@coderline/alphatab/core` exports the full API and starts nothing on its own; you register the worker and worklet with [`Environment.initializeMain`](/docs/features/extend/worker-and-worklet-initialization). [Installation (other bundlers)](/docs/getting-started/installation-other-bundlers) shows the setup for esbuild, Rollup and Parcel. #### bundlers: Turbopack detection +https://github.com/CoderLine/alphaTab/pull/2998 alphaTab detects Turbopack ([`Environment.isTurbopackBundled`](/docs/reference/types/environment), also printed by `Environment.printEnvironmentInfo()`) and starts its workers in the form Turbopack bundles, sharing the core chunk with the page. Turbopack does not bundle Audio Worklets, so there alphaTab loads its worklet from [`core.scriptFile`](/docs/reference/settings/core/scriptfile). See [Next.js with Turbopack](/docs/getting-started/installation-react#turbopack). @@ -403,13 +455,87 @@ alphaTab detects Turbopack ([`Environment.isTurbopackBundled`](/docs/reference/t * bundlers: the minified files reference each other: `alphaTab.min.mjs` imports `alphaTab.core.min.mjs` and starts `alphaTab.worker.min.mjs` and `alphaTab.worklet.min.mjs`, which import the minified core too, so a CDN or copied setup loads only minified files. * player: without a detectable script file, loading the Audio Worklet fails with the error "Could not detect alphaTab script file, cannot initialize audio worklet". +* webpack: `webWorkers: false` in the plugin options turns off the worker bundling again ([#2998](https://github.com/CoderLine/alphaTab/pull/2998)), and the build reports an error instead of hanging when the plugin cannot copy its font and SoundFont files ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* vite: the worker and worklet chunks are no longer manifest entries, so frameworks that inject every manifest entry into the page do not load them as page scripts ([#2998](https://github.com/CoderLine/alphaTab/pull/2998)). On Vite 8 they are minified, and the plugin's type declarations import Vite's `Plugin` type ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* bundlers: in the minified files, `Ottavia._8va` and the other enum members starting with an underscore are defined again, and enum values are inlined across modules, which saves about 10 KB gzip on the minified core ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) + +### Website +https://github.com/CoderLine/alphaTab/pull/2998 + +#### website: new landing page and playground + +The [landing page](/) opens with a full player on Iron Waltz, alphaTab's own demo song: file tabs, a track sidebar, a full-page mode and a switch between the backing track and the synthesizer. Below it, a live alphaTex section renders what you type in an editor with the alphaTex language server (completion, hover, signature help and diagnostics). "Open in Playground" takes the current file or your alphaTex along. + +The [Playground](/playground) is a standalone full-window page. Open a file by picker, drag and drop or from the samples. The URL keeps the state, so Share copies a link to it. It has a track panel with mute, solo, volume and transposition, searchable settings that cover every 1.9 setting, the alphaTex editor (Guitar Pro and other files convert to alphaTex), export to Guitar Pro, alphaTex, WAV, MIDI and print, and a media sync editor for audio files and YouTube videos ([Media Sync Editor](/docs/guides/media-sync-editor)). + +A headphones menu in the navigation bar picks backing track or synthesizer playback and the SoundFont for the whole site: "Standard" (Sonivox) or "High quality" (GeneralUser GS). The demos on the feature pages play only where playback is the point; rendering, import, export and layout demos are silent. Copyrighted demo material is replaced by public-domain arrangements and Iron Waltz. + +#### website: feature catalog and reworked docs + +* The [feature catalog](/docs/features) describes alphaTab by what you want to do, in six groups: import and load, render, play and sync, interact, export and extend. Each page has a purpose-built demo. The old showcase pages redirect to it. +* [Installing in web projects](/docs/getting-started/installation-web-projects) and the pages per bundler and framework replace the old installation pages. The introduction, the getting-started pages and the contributing guide are rewritten. +* New pages: [Attribution](/docs/attribution), [alphaTex troubleshooting](/docs/alphatex/troubleshooting) with every diagnostic code, a [Showcase](/showcase) of projects that use alphaTab, and [`llms.txt`](pathname:///llms.txt) for AI tools. `/docs/releases` always points to the newest release notes. +* The migration section is retired; its pages redirect to the release notes and the getting-started pages. The file format pages are part of the API reference. +* The generated API reference shows deprecation notices on settings, API members and types. + +#### website: part of the alphaTab repository + +The website moved from `CoderLine/alphaTabWebsite`, now archived, into the `website/` folder of the alphaTab repository, with its history. Documentation and release notes change in the same pull request as the code they describe, and "Edit this page" links point there. Report problems with the website or the documentation in the [alphaTab repository](https://github.com/CoderLine/alphaTab/issues/new/choose). + +The site is built with Docusaurus on Rspack, using `@coderline/alphatab-webpack`: a production build takes about 15 s instead of 34 s, and the dev server starts in about 2.5 s instead of 16 s with about 1 GB of memory instead of 2.7 GB. Each nightly and each release builds the site against the packages published from the same commit: nightlies go to [next.alphatab.net](https://next.alphatab.net), releases to alphatab.net. + +### Release hardening +https://github.com/CoderLine/alphaTab/pull/2990 + +Before cutting the release, we reviewed the 1.9 changes across rendering, importers, player and packaging. Each finding is fixed in the layer that causes it and comes with a test. Most items below go back to that review; the fixes listed in the sections above are not repeated here. + +**Rendering and layout** + +* Bend and whammy slurs and tab bends enter the [skyline](/docs/features/render/effect-placement#skyline) with their drawn shape, so "grad.", let ring and other labels no longer sit on them. Dynamics below the staff keep clear of accents. +* Bars squeezed below their natural width take space from the widest gaps first and keep the minimum width of their content. +* Full-bar notes are centered between the bar's start (clef, key and time signature) and its barline; pick-up bars and empty voices no longer prevent the centering. +* Bars with only grace notes place them side by side, dense dynamics get horizontal room, and the ledger lines of neighbouring notes stay apart. +* Beat texts, lyrics, free-time texts and dynamics are spaced only against their own staff; texts on other tracks no longer widen the bar. +* Bar numbers show on multi-bar rests, are no longer clipped at partial boundaries in the horizontal layout, and with [`RenderStylesheet.hideEmptyStaves`](/docs/reference/types/model/renderstylesheet/#hideemptystaves) sit on the first visible track. +* String-number circles are stacked by pitch, and adjacent separate trills are drawn as separate trills ([`Note.isTrillStart`](/docs/reference/types/model/note/#istrillstart) marks where one starts). +* A two-voice unison whose note head is displaced by a second, and beams over rests only in numbered notation (for example a whole rest), render instead of failing. +* Canvas text aligned at the top sits at the same height in Firefox as in Chromium. + +**Importers and model** + +* Unset note values (`NaN`, see [For integrations](#for-integrations)) survive a JSON round trip, and score JSON from 1.8 with `-1` loads as unset. +* Guitar Pro 3–5: the MIDI port and channel are range-checked, a negative fret on a stringed staff becomes a dead note, and notes stored without a fret are dropped. +* Drum tablature is shown according to the file: the staff flags in Guitar Pro 5, the part configuration in Guitar Pro 6–8. Files without the flag show no drum tablature. +* Guitar Pro 6: ties survive on staves that get a generated fingering, and track pan and volume come from the RSE channel strip. +* MusicXML: `measure-numbering` holds until the file changes it, a `` without `` on a tuned staff gets its fret from the pitch, and notes without a tab position get no tab glyphs. +* A custom bar number of `0` is respected. + +**Player and integration** + +* A custom cursor handler set while the cursors already exist is attached, and the previous handler is detached. +* Changes of [`player.scrollMode`](/docs/reference/settings/player/scrollmode) through [`updateSettings()`](/docs/reference/api/updatesettings) take effect. +* When the browser refuses to play a [backing track](/docs/features/play-sync/backing-track-audio), the player pauses instead of reporting that it plays. +* With the worker renderer, bounds resolve against the score that was actually rendered, so a render started while `load()` is still running no longer throws. +* [`exportAudio()`](/docs/reference/api/exportaudio) loads the SoundFont presets the exported song needs. + +**Packaging and platforms** + +* `@coderline/alphatab/vite` and `@coderline/alphatab/webpack` forward to `@coderline/alphatab-vite` and `@coderline/alphatab-webpack`, which `@coderline/alphatab` declares as optional peer dependencies. +* `@coderline/alphatab-language-server` loads with `require`, its `bin` starts with a hashbang, and it and `@coderline/alphatab-monaco` declare the `vscode-languageserver` packages they import. +* [`AlphaTexAtTokenNode`](/docs/reference/types/importer/alphatex/alphatexattokennode) is exported. +* Deprecated members are marked `[Obsolete]` in C# and `@Deprecated` in Kotlin, with their documentation. +* The settings reference documents the actual defaults of [`display.staffPaddingLeft`](/docs/reference/settings/display/staffpaddingleft) and [`display.systemsLayoutMode`](/docs/reference/settings/display/systemslayoutmode), and every API added in 1.9.0 is marked "since 1.9.0". ### Improvements & Bugfixes * render: the horizontal layout with [`display.scale`](/docs/reference/settings/display/scale) above 1 gets its full size, so the end of the score is visible and scrolls with the playhead ([#2894](https://github.com/CoderLine/alphaTab/pull/2894), thanks [@leocaseiro](https://github.com/leocaseiro) for contributing this fix and [@fiskpinne](https://github.com/fiskpinne) for reporting it) -* render: rendering is faster ([#2751](https://github.com/CoderLine/alphaTab/pull/2751)) * platform: alphaTab reads and writes zip archives and decodes SF3 SoundFonts with its own Deflate and Vorbis implementations, so the package no longer carries four third-party license notices ([#2950](https://github.com/CoderLine/alphaTab/pull/2950)) * integration: the [`ICursorHandler`](/docs/reference/types/icursorhandler) type is exported ([3d1f1bf](https://github.com/CoderLine/alphaTab/commit/3d1f1bf2ef720849e024e98657fc816bb2983c26)), together with the two built-in handlers `ToNextBeatAnimatingCursorHandler` (used when [`player.enableAnimatedBeatCursor`](/docs/reference/settings/player/enableanimatedbeatcursor) is on) and `NonAnimatingCursorHandler`, so a custom handler can delegate to them +* integration: with the horizontal layout, [`BoundsLookup.findBeats`](/docs/reference/types/rendering/boundslookup/#findbeats) returns each beat once instead of once per rendered partial, and a partial render that falls back to a full layout no longer adds its systems to the lookup twice ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* integration: errors while the worker reads a score reach the [`error`](/docs/reference/api/error) event ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* integration: the new log level [`LogLevel.Trace`](/docs/reference/types/loglevel) (`Logger.trace`) takes the internal flow details that were logged at debug level. The published JavaScript files contain no trace messages ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* model: the JSON converter accepts `null` colors and fonts, reads fonts from plain JSON objects and writes typed arrays as JSON arrays ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* platform: the IntersectionObserver fallback checks visibility on scroll and resize, GDI+ and Android skip rectangles of zero width, and on .NET and Android numbers are parsed as JavaScript's `parseInt` does. On Kotlin, `splice` on lists returns only the removed elements, and deleting from maps and sets compares keys ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) ### Upgrade Notes @@ -420,6 +546,8 @@ alphaTab detects Turbopack ([`Environment.isTurbopackBundled`](/docs/reference/t * Annotations sit closer to the notes ([#2750](https://github.com/CoderLine/alphaTab/pull/2750)). The default of [`display.effectBandPaddingBottom`](/docs/reference/settings/display/effectbandpaddingbottom) changes from `2` to `5`; raise or lower it to change the gap. See [Effect placement](/docs/features/render/effect-placement#band-padding). * The default of [`display.staffPaddingLeft`](/docs/reference/settings/display/staffpaddingleft) changes from `2` to `6` ([#2907](https://github.com/CoderLine/alphaTab/pull/2907)). Set it to `2` for the previous padding. * Notes and rests that fill a whole bar are centered ([#2781](https://github.com/CoderLine/alphaTab/pull/2781)), and hammer-on, pull-off and slide labels sit above the staff ([#2945](https://github.com/CoderLine/alphaTab/pull/2945)). Hide the labels through [`notation.elements`](/docs/reference/settings/notation/elements); see [Effects and articulations](/docs/features/render/coverage-effects-articulations#slide-labels). +* Numbered notation repeats `0` for rests longer than a quarter, voice colors reach stems, beams, flags and effects, and bend labels on one beat are stacked ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)). The triplet feel indication appears once per bar ([#2992](https://github.com/CoderLine/alphaTab/pull/2992)). +* MusicXML files show dynamics only where the file writes them, on the staff they belong to ([#2996](https://github.com/CoderLine/alphaTab/pull/2996)). #### Changed defaults @@ -446,11 +574,19 @@ The deprecated members still work. * [`AutomationType.Bank`](/docs/reference/types/model/automationtype) changes from `4` to `5`; in 1.8 it had the same value as `AutomationType.SyncPoint`. Score JSON written by 1.8 stores bank changes as `4`, which 1.9 loads as sync points ([#2907](https://github.com/CoderLine/alphaTab/pull/2907)). * Guitar Pro 3–5 files: each track gets the MIDI port and channel stored in the file and its own [`PlaybackInformation`](/docs/reference/types/model/playbackinformation). [`port`](/docs/reference/types/model/playbackinformation/#port) is zero-based (1.8 kept the one-based value of the file), and [`primaryChannel`](/docs/reference/types/model/playbackinformation/#primarychannel) can differ from 1.8, for example `2` instead of `4`. Programs, volume, balance and the notes do not change, and two tracks that 1.8 played on one channel get a channel each. Code that addresses MIDI channels or ports per track sees the new numbers ([#2774](https://github.com/CoderLine/alphaTab/pull/2774)). * Settings JSON ([`RenderingResourcesJson`](/docs/reference/types/json/renderingresourcesjson)) accepts the deprecated fonts `copyrightFont`, `titleFont`, `subTitleFont`, `wordsFont`, `timerFont`, `directionsFont`, `fretboardNumberFont`, `markerFont` and `barNumberFont` and applies them as the properties of the same name do; 1.8 ignored them in JSON. `effectFont`, `fingeringFont` and `inlineFingeringFont` are still accepted and still have no effect ([#2749](https://github.com/CoderLine/alphaTab/pull/2749)). -* The SVG output is no longer identical between two renders of the same score: the staff lines of the bar with the attribution and the attribution itself are written as one path in a random order. The drawn result is the same. Compare rendered images instead of SVG strings. +* The SVG output is no longer identical between two renders of the same score: the staff lines of the bar with the attribution and the attribution itself are written as one path in a random order. The SVG text also has no newlines between elements, black rectangles have no `fill` attribute, and single music symbols end with `;` ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)). The drawn result is the same. Compare rendered images instead of SVG strings. +* The page and its alphaTab worker must load the same alphaTab version: the messages between them changed (MessagePack score, flat bounds lookup) ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)). +* With the render worker, resizes requested while one is still running are combined into one resize with the latest width. The skipped widths raise no [`renderStarted`](/docs/reference/api/renderstarted), [`renderFinished`](/docs/reference/api/renderfinished) or [`postRenderFinished`](/docs/reference/api/postrenderfinished) events ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)). +* With the Audio Worklet, [`playerPositionChanged`](/docs/reference/api/playerpositionchanged) and [`midiEventsPlayed`](/docs/reference/api/midieventsplayed) fire at most every 10 ms by default. Set [`player.positionUpdateIntervalInMilliseconds`](/docs/reference/settings/player/positionupdateintervalinmilliseconds) to `0` for an update per render quantum ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)). +* Internal flow messages are logged at the new [`LogLevel.Trace`](/docs/reference/types/loglevel) instead of `Debug`, and the published JavaScript files do not contain them ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)). +* Fades write the expression controller (CC 11) into the generated MIDI instead of the channel volume (CC 7), and restore it with the voice's next note. MIDI files exported from alphaTab and custom [`IMidiFileHandler`](/docs/reference/types/midi/imidifilehandler) implementations see these events ([#2995](https://github.com/CoderLine/alphaTab/pull/2995)). +* Sync point times, beat timers and other millisecond values derived from ticks keep their decimals instead of being cut to whole milliseconds ([#2960](https://github.com/CoderLine/alphaTab/pull/2960)). +* C# and Kotlin: members deprecated in alphaTab carry `[Obsolete]` and `@Deprecated`, so code that uses them gets compiler warnings ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)). * alphaTex AST consumers: the values of [`AlphaTexNodeType`](/docs/reference/types/importer/alphatex/alphatexnodetype) are renumbered, so compare against the enum members instead of numbers, and `AlphaTexNoteNode.noteStringDot` is now [`noteStringSeparator`](/docs/reference/types/importer/alphatex/alphatexnotenode/#notestringseparator) of type `IAlphaTexStringSeparatorNode` ([#2591](https://github.com/CoderLine/alphaTab/pull/2591)). #### Package changes +* The npm packages are published from GitHub Actions through npm trusted publishing and carry provenance attestations, so you can check which commit and workflow built each version (`npm audit signatures`). Releases go live only after a maintainer approves them ([#2959](https://github.com/CoderLine/alphaTab/pull/2959)). * `@coderline/alphatab-vite/workerPlugin` is removed. Register `alphaTab()` from `@coderline/alphatab-vite`; it covers the worker and the worklet. * `@coderline/alphatab-vite` accepts Vite `^6 || ^7 || ^8` as peer dependency. * `@coderline/alphatab-webpack` declares `webpack` (`^5.0.0`) and `@rspack/core` (`^1.7.0 || ^2.0.0`) as optional peer dependencies; install the one your project builds with. From 996a532817ea76990120618393ccdded2679c80b Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 21:20:03 +0100 Subject: [PATCH 07/14] chore(website): drop the outreach markers from the showcase data --- .../landing/LandingShowcase/stories-data.ts | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/website/src/components/landing/LandingShowcase/stories-data.ts b/website/src/components/landing/LandingShowcase/stories-data.ts index 234450cbb..4ebcfcc7e 100644 --- a/website/src/components/landing/LandingShowcase/stories-data.ts +++ b/website/src/components/landing/LandingShowcase/stories-data.ts @@ -1,9 +1,3 @@ -/** - * `pending`: the author has not yet been asked whether we may feature their words. - * `not-needed`: the entry cites the author's own public credit or announcement. - */ -export type Outreach = 'pending' | 'not-needed'; - export interface ShowcaseLink { label: string; url: string; @@ -71,7 +65,6 @@ export type ShowcaseStory = * top-left, so supply 920×448. Without it the preview shows decorative notation. */ screenshot?: string; - outreach: Outreach; } & ProjectVoice) | { cardType: 'quote'; @@ -84,7 +77,6 @@ export type ShowcaseStory = handle?: string; role: string; source: ShowcaseLink; - outreach: Outreach; } | { cardType: 'press'; @@ -98,14 +90,10 @@ export type ShowcaseStory = quote: string; byline: string; url: string; - outreach: Outreach; }; /** Pool the page draws from on load; the server render uses the first `SHOWCASE_SLOTS` entries of each type. */ export const SHOWCASE_STORIES: ShowcaseStory[] = [ - // TODO(outreach): ask Infinite Guitar for a testimonial to replace the summary, and confirm featuring them - // with the preview: the public "Pentatonic Pathways: Intro to the Scale" lesson page with its header kept, - // the video left out and the author's name and photo blurred. { cardType: 'project', id: 'infiniteguitar', @@ -122,10 +110,7 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ tags: ['learning', 'web'], meta: 'Lessons with Guitar Pro tabs', screenshot: '/img/showcase/infiniteguitar.webp', - outreach: 'pending', }, - // TODO(outreach): ask Fretello for a testimonial to replace the summary, and for consent to use their - // App Store screenshots (fretello.com/assets/images/screenshots/{listen,learn}.jpg) in the preview. { cardType: 'project', id: 'fretello', @@ -142,9 +127,7 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ tags: ['learning', 'mobile'], meta: 'iOS · Android', screenshot: '/img/showcase/fretello.webp', - outreach: 'pending', }, - // TODO(outreach): confirm in discussion #2930 that the app screenshot (our Iron Waltz demo file) may be used. { cardType: 'project', id: 'smart-beat', @@ -163,9 +146,7 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ announcement: 'https://github.com/CoderLine/alphaTab/discussions/2930', meta: 'Tauri · React · desktop', screenshot: '/img/showcase/smart-beat.webp', - outreach: 'pending', }, - // TODO(outreach): Reddit comment by u/louislamlam; confirm the quote and the use of his real name. { cardType: 'project', id: 'its-mytabs', @@ -188,9 +169,7 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ sourceCode: 'https://github.com/louislam/its-mytabs', meta: 'Open source · audio & YouTube sync', screenshot: '/img/showcase/its-mytabs.webp', - outreach: 'pending', }, - // TODO(outreach): Hacker News comment; reply on the thread before treating this as final. { cardType: 'quote', id: 'hn-webprofusion', @@ -201,9 +180,7 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ person: 'webprofusion', role: 'Hacker News commenter', source: { label: 'news.ycombinator.com', url: 'https://news.ycombinator.com/item?id=40835858' }, - outreach: 'pending', }, - // TODO(outreach): courtesy ask on Show and tell #2315 for using the README screenshot of their UI. { cardType: 'project', id: 'tab-flow', @@ -229,9 +206,7 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ // TODO(verify): download count as of 2026-10-17; refresh from the Obsidian plugin stats before release. stat: { k: '1,264', l: 'Obsidian downloads' }, screenshot: '/img/showcase/tab-flow.webp', - outreach: 'pending', }, - // TODO(outreach): courtesy note on Show and tell #2694. { cardType: 'project', id: 'drumforge', @@ -252,7 +227,6 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ announcement: 'https://github.com/CoderLine/alphaTab/discussions/2694', meta: 'Exercises generated as alphaTex', screenshot: '/img/showcase/drumforge.webp', - outreach: 'pending', }, { cardType: 'quote', @@ -265,9 +239,7 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ handle: '@maxcomperatore', role: 'Developer', source: { label: 'GitHub Show and tell', url: 'https://github.com/CoderLine/alphaTab/discussions/2872' }, - outreach: 'not-needed', }, - // TODO(outreach): courtesy ask on Show and tell #1728 for using a screenshot of their UI. { cardType: 'project', id: 'gonb', @@ -289,7 +261,6 @@ export const SHOWCASE_STORIES: ShowcaseStory[] = [ announcement: 'https://github.com/CoderLine/alphaTab/discussions/1728', meta: 'Generated melodies with playback', screenshot: '/img/showcase/gonb.webp', - outreach: 'pending', }, ]; From de44c6604100bd563b8f18cd2e920f47790b36f7 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 21:20:03 +0100 Subject: [PATCH 08/14] fix(website): stop announcing iOS on the platforms card --- website/src/components/landing/LandingFeatures/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/components/landing/LandingFeatures/index.tsx b/website/src/components/landing/LandingFeatures/index.tsx index f62f5c87f..6977a149f 100644 --- a/website/src/components/landing/LandingFeatures/index.tsx +++ b/website/src/components/landing/LandingFeatures/index.tsx @@ -49,7 +49,7 @@ const CARDS: FeatureCard[] = [ { n: '06', t: 'Open core, three runtimes', - d: 'MPL-2.0 license. JavaScript for web, .NET for desktop and server, Kotlin for native Android. iOS in progress.', + d: 'MPL-2.0 license. JavaScript for web, .NET for desktop and server, Kotlin for native Android.', v: , }, ]; From 1b20cb377ed01c4fd65f47f17de07187ea2b640a Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 21:20:03 +0100 Subject: [PATCH 09/14] feat(website): load the prelude tab on the landing page from a real MusicXML file The tab was labelled prelude.musicxml but loaded the Guitar Pro file. Bach_Prelude_BWV999.musicxml holds the same four bars as Bach_Prelude_BWV999.gp: one guitar part with a treble-8vb notation staff and a TAB staff, validated against the MusicXML 4.0 schema. --- .../landing/LandingPlayerStage/demo-files.ts | 5 +- .../static/files/Bach_Prelude_BWV999.musicxml | 145 ++++++++++++++++++ 2 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 website/static/files/Bach_Prelude_BWV999.musicxml diff --git a/website/src/components/landing/LandingPlayerStage/demo-files.ts b/website/src/components/landing/LandingPlayerStage/demo-files.ts index 429f91c24..6bd56f883 100644 --- a/website/src/components/landing/LandingPlayerStage/demo-files.ts +++ b/website/src/components/landing/LandingPlayerStage/demo-files.ts @@ -36,10 +36,9 @@ export const DEMO_FILES: DemoFile[] = [ { id: 'bach-prelude', label: 'prelude.musicxml', - ext: 'mxl', + ext: 'musicxml', trackColor: 'var(--at-track-purple)', - // TODO: swap with a real /files/landing/prelude.musicxml when staged. - source: { url: '/files/Bach_Prelude_BWV999.gp' }, + source: { url: '/files/Bach_Prelude_BWV999.musicxml' }, }, { id: 'bach-prelude-c', diff --git a/website/static/files/Bach_Prelude_BWV999.musicxml b/website/static/files/Bach_Prelude_BWV999.musicxml new file mode 100644 index 000000000..85bfd34f2 --- /dev/null +++ b/website/static/files/Bach_Prelude_BWV999.musicxml @@ -0,0 +1,145 @@ + + + + + Prelude in D Minor + J.S. Bach (1685-1750)Public DomainalphaTab website2026-10-31 + + Classical GuitarGuit.Acoustic Guitar (nylon)125 + + + + 4-1minor2G2-1TAB56E4B3G3D3A2E2 + quarter801 + D31116th1 + D31116th1 + D41116th1 + D31116th1 + F41116th1 + D31116th1 + A41116th1 + F41116th1 + D41116th1 + F41116th1 + D41116th1 + A321eighth1 + D421eighth1 + F321eighth1 + 12 + D31516thnone240 + D31516thnone240 + D41516thnone223 + D31516thnone240 + F41516thnone211 + D31516thnone240 + A41516thnone215 + F41516thnone211 + D41516thnone223 + F41516thnone211 + D41516thnone223 + A325eighthnone232 + D425eighthnone223 + F325eighthnone243 + + + D41116th1 + D31116th1 + D31116th1 + D41116th1 + D31116th1 + F41116th1 + D31116th1 + A41116th1 + F41116th1 + D41116th1 + F41116th1 + D41116th1 + A321eighth1 + D421eighth1 + F321eighth1 + 12 + D41516thnone223 + D31516thnone240 + D31516thnone240 + D41516thnone223 + D31516thnone240 + F41516thnone211 + D31516thnone240 + A41516thnone215 + F41516thnone211 + D41516thnone223 + F41516thnone211 + D41516thnone223 + A325eighthnone232 + D425eighthnone223 + F325eighthnone243 + + + D41116th1 + D31116th1 + D31116th1 + D41116th1 + D31116th1 + G41116th1 + D31116th1 + B-141116th1 + G41116th1 + D41116th1 + G41116th1 + D41116th1 + B-1321eighth1 + D421eighth1 + G321eighth1 + 12 + D41516thnone223 + D31516thnone240 + D31516thnone240 + D41516thnone223 + D31516thnone240 + G41516thnone213 + D31516thnone240 + B-141516thnone216 + G41516thnone213 + D41516thnone223 + G41516thnone213 + D41516thnone223 + B-1325eighthnone233 + D425eighthnone223 + G325eighthnone230 + + + D41116th1 + D31116th1 + D31116th1 + D41116th1 + D31116th1 + G41116th1 + D31116th1 + B-141116th1 + G41116th1 + D41116th1 + G41116th1 + D41116th1 + B-1321eighth1 + D421eighth1 + G321eighth1 + 12 + D41516thnone223 + D31516thnone240 + D31516thnone240 + D41516thnone223 + D31516thnone240 + G41516thnone213 + D31516thnone240 + B-141516thnone216 + G41516thnone213 + D41516thnone223 + G41516thnone213 + D41516thnone223 + B-1325eighthnone233 + D425eighthnone223 + G325eighthnone230 + light-heavy + + + From 58984f3f380c245cb519b78ba500344cfb5fc26d Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 21:26:48 +0100 Subject: [PATCH 10/14] fix(importer): add Capella beats to their voice before computing durations Every .capx file failed with "No compatible importer found for file": the parser called Beat.updateDurations() before the beat was added to its voice and before its notes were read, so the full-bar rest check dereferenced the missing voice. Chords now compute their duration after the notes are parsed, rests after they are added to the voice. --- .../alphatab/src/importer/CapellaParser.ts | 8 +- .../test-data/capella/twinkle-mozart.capx | Bin 0 -> 1168 bytes .../test/importer/CapellaImporter.test.ts | 89 ++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 packages/alphatab/test-data/capella/twinkle-mozart.capx create mode 100644 packages/alphatab/test/importer/CapellaImporter.test.ts diff --git a/packages/alphatab/src/importer/CapellaParser.ts b/packages/alphatab/src/importer/CapellaParser.ts index 1ee00b81d..c7a6eb1f8 100644 --- a/packages/alphatab/src/importer/CapellaParser.ts +++ b/packages/alphatab/src/importer/CapellaParser.ts @@ -704,12 +704,14 @@ export class CapellaParser { chordBeat.preferredBeamDirection = this._currentVoiceState.voiceStemDir; } this._parseDuration(chordBeat, c.findChildElement('duration')!); - chordBeat.updateDurations(); - this._currentVoiceState.currentPosition += chordBeat.playbackDuration; this._currentVoice.addBeat(chordBeat); this._parseChord(chordBeat, c); + // the duration depends on the voice and the notes (full-bar rest detection) + chordBeat.updateDurations(); + this._currentVoiceState.currentPosition += chordBeat.playbackDuration; + if (this._currentVoiceState.currentPosition >= this._currentVoiceState.currentBarDuration) { this._currentVoiceState.currentBarComplete = true; } @@ -718,9 +720,9 @@ export class CapellaParser { const restBeat = this._parseRestDurations(c.findChildElement('duration')!); if (restBeat) { this._initFromPreviousBeat(restBeat, this._currentVoice); + this._currentVoice.addBeat(restBeat); restBeat.updateDurations(); this._currentVoiceState.currentPosition += restBeat.playbackDuration; - this._currentVoice.addBeat(restBeat); if (this._currentVoiceState.currentPosition >= this._currentVoiceState.currentBarDuration) { this._currentVoiceState.currentBarComplete = true; diff --git a/packages/alphatab/test-data/capella/twinkle-mozart.capx b/packages/alphatab/test-data/capella/twinkle-mozart.capx new file mode 100644 index 0000000000000000000000000000000000000000..e783174fb5e6c033fc41378b872320e82b48cd52 GIT binary patch literal 1168 zcmV;B1aJFLO9KQH00ICA07t7|UD}AlPaFgQ05lf>00{s90CQt+a%C=fZEWpWTXW+y z6n^io5PE`{#!8x`T^PkXp=Aq03p=#T@~YT!t*vc&Bsp&O*K;J>iF4`Vp*3Mx9$M>i ze7cZ8H>=>;t3Ny5C4lS`KWaeG6?V5Wb95EWS@ z?Er?XFtJqr2k#&vCG~zK9LO}~nen&Gprl#0s!5}pCTt}OQ@@$B2d+`VBQwKiK$ej? z&^Exa9zz|1?Ew7BQj8~QI!@efZUXvL;Ti4lKJRwU*UoS$K%_#)xhrwgVMXLh8l&tpr;3}1jDlr*olw5`pmj6dJ7b-r=XMOGVC z*l3`PrM$5VvBg61B}_Bv0lMAu-4>wPF|7oAyAMIHCN`Ljx)z3(MHV8rHNz0grA!J7 zH5v|EWRn(E9qB_iKmIj?3#Rp#$)8(VCnH@b7CdsZ$<2D9W@K0~GQub*RI;+ESMW`q zx8J5@Q>(60e_ed!L8F<5ij}b9Q^TJOdUC2)?mtV!0|YMs|F{BLA_=@jEC6TjMG9p9 z!b|jA)*XDT20+&Eoc&ZPY6z0)5Agfzt~LN+@k?O9ibM*&@4w;(@?<)2{4|Tp4cb-GhZH z-A=UDUI3+j2T?;oyK9m?xktWoVv&eUtZlUSWek| z1-`?B>Bz(NF?r|&ULP;Wta1R=kiZA??`3o)!gMis-UnOIS9`}kdj=cYnfkYf%8zB) z#l&}g1m~w?A@3w;DLa+(4}EYDa`kH1J*7w)vrYwk)dxRt7w8@PxdLX2d#`rnYzK#D z;ep7IXD}4}Z&dcz$Cq@eazCVwb)`C#ER&a3$)Wbphtlh#S^64{ z?9frthePxKsqcV&)rUj9#PjmMM*a1;2Zz2Nzx2HQyVU>l=jhG{DN=pXs6KwIKX$;- zexUYG{sd4<0RkQa6aWGM2mnW`UtQXW!%rLp001->000R90000000031005+c00000 ib7OCEWiEJaY*0%D1^@s60096205<>t07C=-0001UIw4yC literal 0 HcmV?d00001 diff --git a/packages/alphatab/test/importer/CapellaImporter.test.ts b/packages/alphatab/test/importer/CapellaImporter.test.ts new file mode 100644 index 000000000..8422de5d8 --- /dev/null +++ b/packages/alphatab/test/importer/CapellaImporter.test.ts @@ -0,0 +1,89 @@ +import { ScoreLoader } from '@coderline/alphatab/importer/ScoreLoader'; +import { IOHelper } from '@coderline/alphatab/io/IOHelper'; +import { Clef } from '@coderline/alphatab/model/Clef'; +import { Duration } from '@coderline/alphatab/model/Duration'; +import type { Score } from '@coderline/alphatab/model/Score'; +import type { Staff } from '@coderline/alphatab/model/Staff'; +import { Settings } from '@coderline/alphatab/Settings'; +import { TestPlatform } from 'test/TestPlatform'; +import { describe, expect, it } from 'vitest'; + +describe('CapellaImporterTests', () => { + function pitches(staff: Staff): number[][] { + return staff.bars.map(b => b.voices[0].beats.map(beat => (beat.isRest ? -1 : beat.notes[0].realValue))); + } + + function capXml(noteObjects: string): Score { + const xml = ` + + + + + + + + + + + + ${noteObjects} + + + + +`; + return ScoreLoader.loadScoreFromBytes(IOHelper.stringToBytes(xml), new Settings()); + } + + it('grand-staff', async () => { + const data = await TestPlatform.loadFile('test-data/capella/twinkle-mozart.capx'); + const score = ScoreLoader.loadScoreFromBytes(data, new Settings()); + + expect(score.title).toBe('Twinkle, Twinkle Little Star'); + expect(score.artist).toBe('W.A. Mozart (1756-1791)'); + expect(score.tracks[0].name).toBe('Piano'); + expect(score.masterBars.length).toBe(4); + expect(score.tempo).toBe(100); + expect(score.tracks.length).toBe(1); + + const [rightHand, leftHand] = score.tracks[0].staves; + expect(score.tracks[0].staves.length).toBe(2); + expect(rightHand.bars[0].clef).toBe(Clef.G2); + expect(leftHand.bars[0].clef).toBe(Clef.F4); + expect(pitches(rightHand)).toEqual([ + [60, 60, 67, 67], + [69, 69, 67], + [65, 65, 64, 64], + [62, 62, 60] + ]); + expect(pitches(leftHand)).toEqual([ + [48, 52, 55, 52], + [53, 57, 55, 52], + [50, 53, 48, 52], + [43, 47, 48] + ]); + expect(rightHand.bars[1].voices[0].beats[2].duration).toBe(Duration.Half); + expect(rightHand.bars[0].voices[0].beats.map(b => b.lyrics![0])).toEqual(['Twin-', 'kle,', 'twin-', 'kle,']); + }); + + it('whole-notes-and-rests', () => { + const score = capXml(` + + + + + + + + + `); + + const staff = score.tracks[0].staves[0]; + expect(score.masterBars.length).toBe(4); + expect(pitches(staff)).toEqual([[60], [-1], [-1], [64, -1]]); + expect(staff.bars[1].voices[0].beats[0].isFullBarRest).toBe(true); + expect(staff.bars[2].voices[0].beats[0].isFullBarRest).toBe(true); + expect(staff.bars[0].voices[0].beats[0].isFullBarRest).toBe(false); + expect(score.masterBars.map(m => m.isAnacrusis)).toEqual([false, false, false, false]); + }); +}); From aa1ba93c748486f5268f4584bf96d9e2a2dc60e4 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 21:26:48 +0100 Subject: [PATCH 11/14] feat(website): load a real Capella file on the landing page and the Capella page The capx tab loaded Drums.gp5. twinkle-mozart.capx holds the K. 265 theme of twinkle-mozart.musicxml on a piano grand staff with lyrics, validated against the CapXML 3.0.3 schema. --- website/docs/features/import-load/capella.mdx | 6 ++---- website/docs/releases/release1_9.mdx | 1 + .../landing/LandingPlayerStage/demo-files.ts | 7 +++---- website/static/files/twinkle-mozart.capx | Bin 0 -> 1168 bytes 4 files changed, 6 insertions(+), 8 deletions(-) create mode 100644 website/static/files/twinkle-mozart.capx diff --git a/website/docs/features/import-load/capella.mdx b/website/docs/features/import-load/capella.mdx index c1f11ff68..2c0743378 100644 --- a/website/docs/features/import-load/capella.mdx +++ b/website/docs/features/import-load/capella.mdx @@ -14,11 +14,9 @@ import { AlphaTab } from '@site/src/components/AlphaTab'; Scores authored in Capella arrive as `.capx` archives — Capella's XML container format. alphaTab imports them so a Capella export opens in the same viewer as your Guitar Pro and MusicXML files, without a separate conversion step. -:::info -The Capella importer routes through the same content-sniffing load path as every other format. The preview below loads a curated Guitar Pro fixture for parity with sibling pages; point `core.file` at a `.capx` file in your own project to exercise the Capella importer end-to-end. -::: +The preview below loads a `.capx` file with the theme of Mozart's variations K. 265 on a piano grand staff, with lyrics under the right hand: - + ## Scope of the Capella importer diff --git a/website/docs/releases/release1_9.mdx b/website/docs/releases/release1_9.mdx index 31948fcf2..87c395aa3 100644 --- a/website/docs/releases/release1_9.mdx +++ b/website/docs/releases/release1_9.mdx @@ -529,6 +529,7 @@ Before cutting the release, we reviewed the 1.9 changes across rendering, import ### Improvements & Bugfixes * render: the horizontal layout with [`display.scale`](/docs/reference/settings/display/scale) above 1 gets its full size, so the end of the score is visible and scrolls with the playhead ([#2894](https://github.com/CoderLine/alphaTab/pull/2894), thanks [@leocaseiro](https://github.com/leocaseiro) for contributing this fix and [@fiskpinne](https://github.com/fiskpinne) for reporting it) +* importer: Capella (`.capx`) files load instead of failing with "No compatible importer found for file"; the importer computed note durations before the notes were part of the score ([Capella (CapXML)](/docs/features/import-load/capella)) * platform: alphaTab reads and writes zip archives and decodes SF3 SoundFonts with its own Deflate and Vorbis implementations, so the package no longer carries four third-party license notices ([#2950](https://github.com/CoderLine/alphaTab/pull/2950)) * integration: the [`ICursorHandler`](/docs/reference/types/icursorhandler) type is exported ([3d1f1bf](https://github.com/CoderLine/alphaTab/commit/3d1f1bf2ef720849e024e98657fc816bb2983c26)), together with the two built-in handlers `ToNextBeatAnimatingCursorHandler` (used when [`player.enableAnimatedBeatCursor`](/docs/reference/settings/player/enableanimatedbeatcursor) is on) and `NonAnimatingCursorHandler`, so a custom handler can delegate to them * integration: with the horizontal layout, [`BoundsLookup.findBeats`](/docs/reference/types/rendering/boundslookup/#findbeats) returns each beat once instead of once per rendered partial, and a partial render that falls back to a full layout no longer adds its systems to the lookup twice ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) diff --git a/website/src/components/landing/LandingPlayerStage/demo-files.ts b/website/src/components/landing/LandingPlayerStage/demo-files.ts index 6bd56f883..3daeb14c5 100644 --- a/website/src/components/landing/LandingPlayerStage/demo-files.ts +++ b/website/src/components/landing/LandingPlayerStage/demo-files.ts @@ -48,11 +48,10 @@ export const DEMO_FILES: DemoFile[] = [ source: { tex: Riffs.BACH_PRELUDE_C_LONG }, }, { - id: 'drums', - label: 'etude.capx', + id: 'twinkle', + label: 'twinkle.capx', ext: 'capx', trackColor: 'var(--at-track-orange)', - // TODO: swap with a real /files/landing/etude.capx when staged. - source: { url: '/files/features/Drums.gp5' }, + source: { url: '/files/twinkle-mozart.capx' }, }, ]; diff --git a/website/static/files/twinkle-mozart.capx b/website/static/files/twinkle-mozart.capx new file mode 100644 index 0000000000000000000000000000000000000000..e783174fb5e6c033fc41378b872320e82b48cd52 GIT binary patch literal 1168 zcmV;B1aJFLO9KQH00ICA07t7|UD}AlPaFgQ05lf>00{s90CQt+a%C=fZEWpWTXW+y z6n^io5PE`{#!8x`T^PkXp=Aq03p=#T@~YT!t*vc&Bsp&O*K;J>iF4`Vp*3Mx9$M>i ze7cZ8H>=>;t3Ny5C4lS`KWaeG6?V5Wb95EWS@ z?Er?XFtJqr2k#&vCG~zK9LO}~nen&Gprl#0s!5}pCTt}OQ@@$B2d+`VBQwKiK$ej? z&^Exa9zz|1?Ew7BQj8~QI!@efZUXvL;Ti4lKJRwU*UoS$K%_#)xhrwgVMXLh8l&tpr;3}1jDlr*olw5`pmj6dJ7b-r=XMOGVC z*l3`PrM$5VvBg61B}_Bv0lMAu-4>wPF|7oAyAMIHCN`Ljx)z3(MHV8rHNz0grA!J7 zH5v|EWRn(E9qB_iKmIj?3#Rp#$)8(VCnH@b7CdsZ$<2D9W@K0~GQub*RI;+ESMW`q zx8J5@Q>(60e_ed!L8F<5ij}b9Q^TJOdUC2)?mtV!0|YMs|F{BLA_=@jEC6TjMG9p9 z!b|jA)*XDT20+&Eoc&ZPY6z0)5Agfzt~LN+@k?O9ibM*&@4w;(@?<)2{4|Tp4cb-GhZH z-A=UDUI3+j2T?;oyK9m?xktWoVv&eUtZlUSWek| z1-`?B>Bz(NF?r|&ULP;Wta1R=kiZA??`3o)!gMis-UnOIS9`}kdj=cYnfkYf%8zB) z#l&}g1m~w?A@3w;DLa+(4}EYDa`kH1J*7w)vrYwk)dxRt7w8@PxdLX2d#`rnYzK#D z;ep7IXD}4}Z&dcz$Cq@eazCVwb)`C#ER&a3$)Wbphtlh#S^64{ z?9frthePxKsqcV&)rUj9#PjmMM*a1;2Zz2Nzx2HQyVU>l=jhG{DN=pXs6KwIKX$;- zexUYG{sd4<0RkQa6aWGM2mnW`UtQXW!%rLp001->000R90000000031005+c00000 ib7OCEWiEJaY*0%D1^@s60096205<>t07C=-0001UIw4yC literal 0 HcmV?d00001 From 8c61628159daa1c6dba667f4975f96967ee0b0ed Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Sat, 31 Oct 2026 21:33:48 +0100 Subject: [PATCH 12/14] docs(releases): list the release review fixes under their features instead of a hardening section --- website/docs/releases/release1_9.mdx | 80 +++++++++++----------------- 1 file changed, 31 insertions(+), 49 deletions(-) diff --git a/website/docs/releases/release1_9.mdx b/website/docs/releases/release1_9.mdx index 87c395aa3..17fe30457 100644 --- a/website/docs/releases/release1_9.mdx +++ b/website/docs/releases/release1_9.mdx @@ -7,7 +7,7 @@ import { AlphaTexSample } from '@site/src/components/AlphaTexSample'; ## 1.9.0 -This release gives the alphaTab attribution a smaller, quieter place in the score, renders and resizes in about half the time and memory, reworks note spacing and the placement of annotations, adds per-staff control over clefs, key and time signatures and bar numbers, plays repeats and jumps in the right order, imports MusicXML files more faithfully, and loads alphaTab's core once for the page, its workers and its Audio Worklet with webpack, Rspack, Vite and Turbopack. Before the release we reviewed all of these changes again and fixed what the [review](#release-hardening) found. The website is rebuilt and now lives in the alphaTab repository. The [upgrade notes](#upgrade-notes) at the end list the visual changes, changed defaults and deprecations. +This release gives the alphaTab attribution a smaller, quieter place in the score, renders and resizes in about half the time and memory, reworks note spacing and the placement of annotations, adds per-staff control over clefs, key and time signatures and bar numbers, plays repeats and jumps in the right order, imports MusicXML files more faithfully, and loads alphaTab's core once for the page, its workers and its Audio Worklet with webpack, Rspack, Vite and Turbopack. The website is rebuilt and now lives in the alphaTab repository. The [upgrade notes](#upgrade-notes) at the end list the visual changes, changed defaults and deprecations. ### Attribution @@ -67,6 +67,7 @@ https://github.com/CoderLine/alphaTab/pull/2997 #### render: power-law note spacing and last-system justification https://github.com/CoderLine/alphaTab/pull/2749 +https://github.com/CoderLine/alphaTab/pull/2990 alphaTab spaces notes with a power-law model, the approach Dorico, MuseScore and Finale use: doubling a duration multiplies its width by [`display.spacingRatio`](/docs/reference/settings/display/spacingratio). The default is `Math.SQRT2` (about 1.414, as in Dorico); values are clamped to 1.2–2.0. Whole notes and rest bars follow the same ratio, so how much of a system they take depends on `spacingRatio`. [`display.stretchForce`](/docs/reference/settings/display/stretchforce) still sets the overall density. @@ -84,12 +85,15 @@ The same four bars with `spacingRatio` 1.2 (first) and the default (second): [`display.lastSystemFillThreshold`](/docs/reference/settings/display/lastsystemfillthreshold) decides when the last system stretches to the full width: once its natural width reaches the given fraction of the available width. `1` (default) never stretches it, `0` always does, and values between `0.4` and `0.7` behave like Dorico and MuseScore. It replaces [`display.justifyLastSystem`](/docs/reference/settings/display/justifylastsystem). +A bar squeezed below its natural width takes the space from its widest gaps first and keeps the minimum width of its content. + Full demo and all options: [Density and spacing](/docs/features/render/density-and-spacing). Thanks to [@AvaTheArchitect](https://github.com/AvaTheArchitect) for the discussion that led to this. #### render: effect placement against the notation https://github.com/CoderLine/alphaTab/pull/2750 +https://github.com/CoderLine/alphaTab/pull/2990 Texts, chord names, dynamics, palm-mute and let-ring brackets and the other annotations are placed against the outline of the notation (the skyline) instead of in rows spanning the whole system. An annotation moves away from the staff only where a note, stem, beam or another annotation is in the way. Charts with many annotations need less vertical space. @@ -100,7 +104,7 @@ Texts, chord names, dynamics, palm-mute and let-ring brackets and the other anno :8 0.6{pm} 0.6{pm} 3.6{pm} 0.6{pm} :16 12.1{txt "solo" h} 15.1{h} 12.1 14.2 :8 17.1{v} 12.1 `} -`Am` and `rit.` sit right above the low notes of the first bar, while the high sixteenth notes in the second bar push the labels and `solo` up. The placement is always active. [`display.effectBandPaddingBottom`](/docs/reference/settings/display/effectbandpaddingbottom) sets the gap between an annotation row and the staff or the row below it. +`Am` and `rit.` sit right above the low notes of the first bar, while the high sixteenth notes in the second bar push the labels and `solo` up. Bends, whammy bars and their slurs enter the skyline with their drawn shape, so "grad.", let ring and other labels keep clear of them, and dynamics below the staff keep clear of accents. The placement is always active. [`display.effectBandPaddingBottom`](/docs/reference/settings/display/effectbandpaddingbottom) sets the gap between an annotation row and the staff or the row below it. Full demo and all options: [Effect placement](/docs/features/render/effect-placement#skyline). @@ -122,6 +126,7 @@ Full demo and all options: [Staff element display](/docs/features/render/staff-e #### notation: bar numbering with pick-up bars, custom numbers and texts https://github.com/CoderLine/alphaTab/pull/2907 +https://github.com/CoderLine/alphaTab/pull/2990 Bar numbers belong to the master bar. Pick-up bars get no number and do not count. [`MasterBar.customBarNumber`](/docs/reference/types/model/masterbar/#custombarnumber) restarts the count, and [`MasterBar.customBarNumberText`](/docs/reference/types/model/masterbar/#custombarnumbertext) shows a text such as `1a` instead of the number. In alphaTex, [`\barNumber`](/docs/alphatex/bar-metadata#barnumber) sets either one. @@ -133,7 +138,7 @@ Bar numbers belong to the master bar. Pick-up bars get no number and do not coun \\barNumber 5 :1 C5 `} -The pick-up bar has no number, the third bar shows `1a`, and the last bar restarts the count at 5. By default, alphaTab now numbers only the first bar of each system. The number is centered above the start barline and no longer adds space at the start of the bar. Clef, key signature and time signature line up across the staves of a system. The MusicXML importer reads the `number` attribute of each measure. +The pick-up bar has no number, the third bar shows `1a`, and the last bar restarts the count at 5. By default, alphaTab now numbers only the first bar of each system. The number is centered above the start barline and no longer adds space at the start of the bar. Clef, key signature and time signature line up across the staves of a system. Bar numbers also show on multi-bar rests, are not clipped at the edges of the rendered parts in the horizontal layout, and with [`RenderStylesheet.hideEmptyStaves`](/docs/reference/types/model/renderstylesheet/#hideemptystaves) sit on the first visible track. A custom bar number of `0` is kept. The MusicXML importer reads the `number` attribute of each measure; `measure-numbering` holds until the file changes it. Full demo and all options: [Bar numbers](/docs/features/render/bar-numbers#display-modes). @@ -158,6 +163,7 @@ Thanks to [@kulek1](https://github.com/kulek1) for contributing this feature, an #### notation: drum tablature https://github.com/CoderLine/alphaTab/pull/2591 +https://github.com/CoderLine/alphaTab/pull/2990 Percussion staves can render as a drum tablature, as in Guitar Pro and TuxGuitar. Each drum piece sits on a line, and the number on the line is its articulation number. Turn it on with [`Staff.showTablature`](/docs/reference/types/model/staff/#showtablature), or with `\staff { score tabs }` in alphaTex. In alphaTex, `@` assigns the line of a percussion note: `SnareHit@3` places the snare on line 3. @@ -169,7 +175,7 @@ Percussion staves can render as a drum tablature, as in Guitar Pro and TuxGuitar :4 (KickHit@5 HiHatClosed@1) (SnareHit@3 HiHatClosed@1) (KickHit@5 HiHatClosed@1) (SnareHit@3 HiHatClosed@1) `} -Guitar Pro 7 and 8 files keep the line of every drum note on import and export. +Guitar Pro files show the drum tablature as they store it: the staff flags in Guitar Pro 5, the part configuration in Guitar Pro 6–8; files without it show none. Guitar Pro 7 and 8 files keep the line of every drum note on import and export. Full demo and all options: [Drum and percussion notation](/docs/features/render/drum-tabs#drum-tablature). @@ -196,8 +202,9 @@ Thanks to [@shimabox](https://github.com/shimabox) for reporting the problem wit #### notation: centered full-bar notes and rests https://github.com/CoderLine/alphaTab/pull/2781 +https://github.com/CoderLine/alphaTab/pull/2990 -When a bar holds a single beat that lasts the whole bar, alphaTab centers it in the bar together with its annotations, following Behind Bars. This applies when every staff and voice rendered in the bar has one such beat; other bars keep the regular positioning. +When a bar holds a single beat that lasts the whole bar, alphaTab centers it between the start of the bar (after clef, key and time signature) and the barline, together with its annotations, following Behind Bars. This applies when every staff and voice rendered in the bar has one such beat, also in pick-up bars and next to empty voices; other bars keep the regular positioning. {` \\hideDynamics @@ -234,6 +241,12 @@ Thanks to [@roscopeeco](https://github.com/roscopeeco) for contributing this fea * render: the bend labels of several bent notes on one beat are merged and stacked instead of drawn over each other ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) * render: the triplet feel indication is drawn once per bar instead of once per voice and staff, and the capo once per staff ([#2992](https://github.com/CoderLine/alphaTab/pull/2992), thanks [@parvanitis15](https://github.com/parvanitis15)) * render: dead-slapped beats of whole duration or longer, and beams made only of dead slaps, render instead of failing with `Cannot read properties of null (reading 'beat')` ([#2993](https://github.com/CoderLine/alphaTab/pull/2993), thanks [@yezhenbiao](https://github.com/yezhenbiao)) +* render: a two-voice unison whose note head is displaced by a second, and beams over rests only in numbered notation (for example a whole rest), render instead of failing ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* render: bars with only grace notes place them side by side, dense dynamics get horizontal room, and the ledger lines of neighbouring notes stay apart ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* render: beat texts, lyrics, free-time texts and dynamics are spaced only against their own staff; texts on other tracks no longer widen the bar ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* render: string-number circles on one beat are stacked by pitch ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* render: adjacent separate trills are drawn as separate trills; [`Note.isTrillStart`](/docs/reference/types/model/note/#istrillstart) marks where one starts ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* render: top-aligned text on the HTML5 canvas sits at the same height in Firefox as in Chromium ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) ### Player @@ -324,6 +337,11 @@ Thanks to [@AvaTheArchitect](https://github.com/AvaTheArchitect) for the discuss * player: with a [backing track](/docs/features/play-sync/backing-track-audio) or [external media](/docs/features/play-sync/video-sync), the playback position no longer falls further behind the audio over the song at tempos where a bar does not last a whole number of milliseconds, such as 148 BPM. Sync point times and beat timers keep their decimals ([#2960](https://github.com/CoderLine/alphaTab/pull/2960), thanks [@bj0rge](https://github.com/bj0rge)) * player: fade in, fade out and volume swell ramp the expression controller (CC 11) instead of the channel volume (CC 7), so a fade scales the volume in effect. After a fade out or volume swell the track plays again with the voice's next note; before, it stayed silent until the end and [`changeTrackVolume`](/docs/reference/api/changetrackvolume) could not bring it back ([#2995](https://github.com/CoderLine/alphaTab/pull/2995), thanks [@yoyecros](https://github.com/yoyecros)) * player: a local `initialAttenuation` in a SoundFont zone replaces the global value instead of adding to it, as the SoundFont 2.04 specification says, so affected presets are no longer too quiet ([#2994](https://github.com/CoderLine/alphaTab/pull/2994), thanks [@fanjink](https://github.com/fanjink)) +* player: changes of [`player.scrollMode`](/docs/reference/settings/player/scrollmode) through [`updateSettings()`](/docs/reference/api/updatesettings) take effect ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* player: when the browser refuses to play a [backing track](/docs/features/play-sync/backing-track-audio), the player pauses instead of reporting that it plays ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* player: [`exportAudio()`](/docs/reference/api/exportaudio) loads the SoundFont presets the exported song needs ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* integration: a custom cursor handler set while the cursors already exist is attached, and the previous handler is detached ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* integration: with the worker renderer, bounds resolve against the score that was actually rendered, so a render started while `load()` is still running no longer throws ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) ### alphaTex Extensions @@ -352,6 +370,7 @@ Full demo and all options: [Staff element display](/docs/features/render/staff-e * alphaTex: harmonic values can be fractional, as in `3.3{ah 2.4}`, so exported files with such values load again ([#2941](https://github.com/CoderLine/alphaTab/pull/2941), thanks [@stewacide](https://github.com/stewacide)) * alphaTex: the exporter writes [`acc`](/docs/alphatex/note-properties#acc) only where the accidental changes the spelling ([#2931](https://github.com/CoderLine/alphaTab/pull/2931), thanks [@gabbe](https://github.com/gabbe)) * alphaTex: the millisecond offset of [`\sync`](/docs/alphatex/bar-metadata#sync) can have decimals, also in the form without parentheses: `\sync 1 0 1621.62` no longer splits into a sync point at 1621 ms and a note `62` ([#2960](https://github.com/CoderLine/alphaTab/pull/2960)) +* alphaTex: [`AlphaTexAtTokenNode`](/docs/reference/types/importer/alphatex/alphatexattokennode) is exported ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) * alphaTex: an export and re-import keeps empty Guitar Pro voices, their dynamics and the strings of Guitar Pro 7 and 8 drum notes ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) * alphaTex: `basicEditorLspIntegration` of `@coderline/alphatab-monaco` answers only for its editor's model, so several editors can each use a language server, and returns a disposable; `diagnosticsDelay` and `completionRange` tune diagnostics and completions (see [Monaco](/docs/alphatex/monaco#setup-the-monaco-lsp-integration)) * alphaTex: `@coderline/alphatab-language-server` and `@coderline/alphatab-monaco` bundle and run again; the language server has separate entry points for browsers and Node.js ([#2830](https://github.com/CoderLine/alphaTab/pull/2830); see [Language server](/docs/alphatex/lsp), [Monaco](/docs/alphatex/monaco) and the [package changes](#package-changes)) @@ -384,6 +403,7 @@ Full list of supported elements: [MusicXML feature table](/docs/formats/musicxml * musicxml: high voice numbers are packed into consecutive voices per staff ([#2831](https://github.com/CoderLine/alphaTab/pull/2831)) * musicxml: a dynamic is shown at the next note of its staff (and voice, if given) instead of the next beat in the file, so it no longer appears on other staves and parts, and no `f` is drawn where the file writes none. Dynamics attached to notes (``) are shown too. For playback a dynamic applies to all staves of its part, as in MuseScore and Dorico; of two dynamics at the same position, the staff's own wins. The new [`Beat.showDynamics`](/docs/reference/types/model/beat/#showdynamics) carries this; other importers leave it unset and render as before ([#2996](https://github.com/CoderLine/alphaTab/pull/2996), thanks [@weblings](https://github.com/weblings)) * musicxml: the `number` attribute of each measure sets custom bar numbers and texts, and implicit measures become pick-up bars ([#2907](https://github.com/CoderLine/alphaTab/pull/2907); see [Bar numbers](/docs/features/render/bar-numbers#file-import)) +* musicxml: a `` without `` on a tuned staff gets its fret from the pitch, and notes without a tab position get no tab glyphs ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) ### Guitar Pro Extensions @@ -420,6 +440,8 @@ Thanks to [@chenkigba](https://github.com/chenkigba) for contributing this chang * importer/exporter: the drum tablature lines of Guitar Pro 7 and 8 files are kept on import and export ([#2591](https://github.com/CoderLine/alphaTab/pull/2591), thanks [@leocaseiro](https://github.com/leocaseiro); see [Drum tablature](/docs/features/render/drum-tabs#drum-tablature)) * importer: a malformed GPX file whose data lies before its file entry no longer makes the import loop forever until it runs out of memory. Invalid sector pointers, truncated entries and file sizes outside the container fail with an [`UnsupportedFormatError`](/docs/reference/types/importer/unsupportedformaterror), and the decompressed size respects [`importer.maxDecodingBufferSize`](/docs/reference/settings/importer/maxdecodingbuffersize) ([#2991](https://github.com/CoderLine/alphaTab/pull/2991), thanks [@CaliLuke](https://github.com/CaliLuke)) * importer: Guitar Pro 3–5 drum grace notes are read as percussion notes, the note duration percentage is read correctly (several files had broken values), and notes without a fingering get "unknown finger" ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* importer: Guitar Pro 3–5 MIDI ports and channels are range-checked, a negative fret on a stringed staff becomes a dead note, and notes stored without a fret are dropped ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* importer: in Guitar Pro 6 files, ties survive on staves that get a generated fingering, and track pan and volume come from the RSE channel strip ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) * exporter: beat timers are written to Guitar Pro 7 files as whole milliseconds, as the format stores them ([#2960](https://github.com/CoderLine/alphaTab/pull/2960)) ### Bundlers and Frameworks @@ -476,7 +498,7 @@ A headphones menu in the navigation bar picks backing track or synthesizer playb * [Installing in web projects](/docs/getting-started/installation-web-projects) and the pages per bundler and framework replace the old installation pages. The introduction, the getting-started pages and the contributing guide are rewritten. * New pages: [Attribution](/docs/attribution), [alphaTex troubleshooting](/docs/alphatex/troubleshooting) with every diagnostic code, a [Showcase](/showcase) of projects that use alphaTab, and [`llms.txt`](pathname:///llms.txt) for AI tools. `/docs/releases` always points to the newest release notes. * The migration section is retired; its pages redirect to the release notes and the getting-started pages. The file format pages are part of the API reference. -* The generated API reference shows deprecation notices on settings, API members and types. +* The generated API reference shows deprecation notices on settings, API members and types, marks every API added in 1.9.0 with "since 1.9.0", and documents the actual defaults of [`display.staffPaddingLeft`](/docs/reference/settings/display/staffpaddingleft) and [`display.systemsLayoutMode`](/docs/reference/settings/display/systemslayoutmode) ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)). #### website: part of the alphaTab repository @@ -484,48 +506,6 @@ The website moved from `CoderLine/alphaTabWebsite`, now archived, into the `webs The site is built with Docusaurus on Rspack, using `@coderline/alphatab-webpack`: a production build takes about 15 s instead of 34 s, and the dev server starts in about 2.5 s instead of 16 s with about 1 GB of memory instead of 2.7 GB. Each nightly and each release builds the site against the packages published from the same commit: nightlies go to [next.alphatab.net](https://next.alphatab.net), releases to alphatab.net. -### Release hardening -https://github.com/CoderLine/alphaTab/pull/2990 - -Before cutting the release, we reviewed the 1.9 changes across rendering, importers, player and packaging. Each finding is fixed in the layer that causes it and comes with a test. Most items below go back to that review; the fixes listed in the sections above are not repeated here. - -**Rendering and layout** - -* Bend and whammy slurs and tab bends enter the [skyline](/docs/features/render/effect-placement#skyline) with their drawn shape, so "grad.", let ring and other labels no longer sit on them. Dynamics below the staff keep clear of accents. -* Bars squeezed below their natural width take space from the widest gaps first and keep the minimum width of their content. -* Full-bar notes are centered between the bar's start (clef, key and time signature) and its barline; pick-up bars and empty voices no longer prevent the centering. -* Bars with only grace notes place them side by side, dense dynamics get horizontal room, and the ledger lines of neighbouring notes stay apart. -* Beat texts, lyrics, free-time texts and dynamics are spaced only against their own staff; texts on other tracks no longer widen the bar. -* Bar numbers show on multi-bar rests, are no longer clipped at partial boundaries in the horizontal layout, and with [`RenderStylesheet.hideEmptyStaves`](/docs/reference/types/model/renderstylesheet/#hideemptystaves) sit on the first visible track. -* String-number circles are stacked by pitch, and adjacent separate trills are drawn as separate trills ([`Note.isTrillStart`](/docs/reference/types/model/note/#istrillstart) marks where one starts). -* A two-voice unison whose note head is displaced by a second, and beams over rests only in numbered notation (for example a whole rest), render instead of failing. -* Canvas text aligned at the top sits at the same height in Firefox as in Chromium. - -**Importers and model** - -* Unset note values (`NaN`, see [For integrations](#for-integrations)) survive a JSON round trip, and score JSON from 1.8 with `-1` loads as unset. -* Guitar Pro 3–5: the MIDI port and channel are range-checked, a negative fret on a stringed staff becomes a dead note, and notes stored without a fret are dropped. -* Drum tablature is shown according to the file: the staff flags in Guitar Pro 5, the part configuration in Guitar Pro 6–8. Files without the flag show no drum tablature. -* Guitar Pro 6: ties survive on staves that get a generated fingering, and track pan and volume come from the RSE channel strip. -* MusicXML: `measure-numbering` holds until the file changes it, a `` without `` on a tuned staff gets its fret from the pitch, and notes without a tab position get no tab glyphs. -* A custom bar number of `0` is respected. - -**Player and integration** - -* A custom cursor handler set while the cursors already exist is attached, and the previous handler is detached. -* Changes of [`player.scrollMode`](/docs/reference/settings/player/scrollmode) through [`updateSettings()`](/docs/reference/api/updatesettings) take effect. -* When the browser refuses to play a [backing track](/docs/features/play-sync/backing-track-audio), the player pauses instead of reporting that it plays. -* With the worker renderer, bounds resolve against the score that was actually rendered, so a render started while `load()` is still running no longer throws. -* [`exportAudio()`](/docs/reference/api/exportaudio) loads the SoundFont presets the exported song needs. - -**Packaging and platforms** - -* `@coderline/alphatab/vite` and `@coderline/alphatab/webpack` forward to `@coderline/alphatab-vite` and `@coderline/alphatab-webpack`, which `@coderline/alphatab` declares as optional peer dependencies. -* `@coderline/alphatab-language-server` loads with `require`, its `bin` starts with a hashbang, and it and `@coderline/alphatab-monaco` declare the `vscode-languageserver` packages they import. -* [`AlphaTexAtTokenNode`](/docs/reference/types/importer/alphatex/alphatexattokennode) is exported. -* Deprecated members are marked `[Obsolete]` in C# and `@Deprecated` in Kotlin, with their documentation. -* The settings reference documents the actual defaults of [`display.staffPaddingLeft`](/docs/reference/settings/display/staffpaddingleft) and [`display.systemsLayoutMode`](/docs/reference/settings/display/systemslayoutmode), and every API added in 1.9.0 is marked "since 1.9.0". - ### Improvements & Bugfixes * render: the horizontal layout with [`display.scale`](/docs/reference/settings/display/scale) above 1 gets its full size, so the end of the score is visible and scrolls with the playhead ([#2894](https://github.com/CoderLine/alphaTab/pull/2894), thanks [@leocaseiro](https://github.com/leocaseiro) for contributing this fix and [@fiskpinne](https://github.com/fiskpinne) for reporting it) @@ -570,7 +550,7 @@ The deprecated members still work. * With `@coderline/alphatab-webpack`, alphaTab's core is a chunk of its own that the page loads next to your entry script. HTML generated by `HtmlWebpackPlugin`, `HtmlRspackPlugin`, Rsbuild or Next.js includes it. A hand-written `
${category}
${category}
\`${member.name.getText()}\`\`${member.name.getText()}\`