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/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 }} diff --git a/.github/workflows/~publish_website.yml b/.github/workflows/~publish_website.yml index f5368351a..694ac8a51 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 @@ -150,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 diff --git a/packages/alphatab/src/importer/CapellaParser.ts b/packages/alphatab/src/importer/CapellaParser.ts index 1ee00b81d..c33343a1f 100644 --- a/packages/alphatab/src/importer/CapellaParser.ts +++ b/packages/alphatab/src/importer/CapellaParser.ts @@ -550,7 +550,7 @@ export class CapellaParser { } private _voiceStates: Map = new Map(); - private _currentVoiceState!: CapellaVoiceState; + private _currentVoiceState: CapellaVoiceState = new CapellaVoiceState(); private _currentBar!: Bar; private _currentVoice!: Voice; @@ -572,7 +572,7 @@ export class CapellaParser { element: XmlNode ) { const voiceStateKey = `${staffId}_${voiceIndex}`; - if (this._currentVoiceState && !this._currentVoiceState.currentBarComplete) { + if (!this._currentVoiceState.currentBarComplete) { this._currentBar.masterBar.isAnacrusis = true; } @@ -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 000000000..e783174fb Binary files /dev/null and b/packages/alphatab/test-data/capella/twinkle-mozart.capx differ diff --git a/packages/alphatab/test/importer/CapellaImporter.test.ts b/packages/alphatab/test/importer/CapellaImporter.test.ts new file mode 100644 index 000000000..27e0a4fef --- /dev/null +++ b/packages/alphatab/test/importer/CapellaImporter.test.ts @@ -0,0 +1,90 @@ +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); + + expect(score.tracks[0].staves.length).toBe(2); + const rightHand = score.tracks[0].staves[0]; + const leftHand = score.tracks[0].staves[1]; + 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]); + }); +}); 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/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/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/docs/releases/release1_9.mdx b/website/docs/releases/release1_9.mdx index f14f8f015..d32da2f83 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 faster with about half the 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 @@ -30,6 +30,16 @@ 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 rendering and resizing +https://github.com/CoderLine/alphaTab/pull/2997 +https://github.com/CoderLine/alphaTab/pull/2751 + +Compared with 1.8.4, rendering a song takes about a third less time, resizing up to 40 % less, and both allocate about half the memory. A resize now gives the same layout as a fresh render at that width. In the browser, the worker exchanges less data with the page, parts of the score far out of view release their memory, and the MIDI is generated once per load. + +Full demo and all options: [Responsive resize](/docs/features/render/responsive-resize). + ### Music Notation #### render: power-law note spacing and last-system justification @@ -100,7 +110,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. Multi-bar rests show their number too. The MusicXML importer reads the `number` attribute of each measure. Full demo and all options: [Bar numbers](/docs/features/render/bar-numbers#display-modes). @@ -125,6 +135,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. @@ -136,7 +147,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 where the file turns it on, and 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,6 +207,10 @@ 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 writes rests longer than a quarter as repeated `0`, voice colors reach stems, beams, flags and effects, and bend labels on one beat are stacked ([#2997](https://github.com/CoderLine/alphaTab/pull/2997), see [Per-element colors](/docs/features/render/per-element-colors)) +* render: triplet feel is shown once per bar and the capo once per staff ([#2992](https://github.com/CoderLine/alphaTab/pull/2992), thanks [@parvanitis15](https://github.com/parvanitis15)) +* render: bend and whammy annotations, grace-only bars, dense dynamics, ledger lines, string-number circles and texts of other tracks get the room they need, and adjacent trills stay separate ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* render: dead-slapped beats, displaced unisons and rest-only beams in numbered notation no longer fail to render ([#2993](https://github.com/CoderLine/alphaTab/pull/2993), [#2990](https://github.com/CoderLine/alphaTab/pull/2990), thanks [@yezhenbiao](https://github.com/yezhenbiao)) ### Player @@ -283,6 +298,10 @@ 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: backing tracks and external media no longer drift behind the audio at tempos like 148 BPM ([#2960](https://github.com/CoderLine/alphaTab/pull/2960), thanks [@bj0rge](https://github.com/bj0rge)) +* player: after a fade out or volume swell the track plays again instead of staying silent; fades use the expression controller (CC 11) ([#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, so affected presets are no longer too quiet ([#2994](https://github.com/CoderLine/alphaTab/pull/2994), thanks [@fanjink](https://github.com/fanjink)) +* player: `player.scrollMode` changes through `updateSettings()` take effect, a backing track the browser refuses to play pauses the player, audio export loads the presets it needs, and custom cursor handlers set later are attached ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) ### alphaTex Extensions @@ -310,6 +329,7 @@ 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: [`\sync`](/docs/alphatex/bar-metadata#sync) accepts decimal millisecond offsets, and an export and re-import keeps empty Guitar Pro voices and drum note strings ([#2960](https://github.com/CoderLine/alphaTab/pull/2960), [#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,7 +359,9 @@ 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: dynamics are shown only where the file writes them, on their own staff, and apply to all staves of the part for playback ([#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 `` gets its fret from the pitch, and `measure-numbering` holds until the file changes it ([#2990](https://github.com/CoderLine/alphaTab/pull/2990)) ### Guitar Pro Extensions @@ -374,28 +396,35 @@ 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 fails with an error instead of looping until it runs out of memory ([#2991](https://github.com/CoderLine/alphaTab/pull/2991), thanks [@CaliLuke](https://github.com/CaliLuke)) +* importer: Guitar Pro 3–5 files read drum grace notes, note durations, fingerings, frets and MIDI channels correctly, and Guitar Pro 6 files keep ties and the track pan and volume ([#2997](https://github.com/CoderLine/alphaTab/pull/2997), [#2990](https://github.com/CoderLine/alphaTab/pull/2990)) ### 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 +432,27 @@ 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 and vite: `webWorkers: false` turns off the worker bundling, the webpack plugin reports copy errors instead of hanging, and the Vite worker and worklet chunks are minified and no longer manifest entries ([#2998](https://github.com/CoderLine/alphaTab/pull/2998), [#2997](https://github.com/CoderLine/alphaTab/pull/2997), [#2990](https://github.com/CoderLine/alphaTab/pull/2990)) +* bundlers: `Ottavia._8va` and the other enum members starting with an underscore are defined in the minified files again ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) + +### Website +https://github.com/CoderLine/alphaTab/pull/2998 + +The website is rebuilt and moved into the alphaTab repository, so documentation and release notes now ship in the same pull request as the code. Report website problems in the [alphaTab repository](https://github.com/CoderLine/alphaTab/issues/new/choose). + +* A new [landing page](/) with a full player and a live alphaTex editor, and a standalone [Playground](/playground) with track mixer, settings, export and a [media sync editor](/docs/guides/media-sync-editor). +* A [feature catalog](/docs/features) with a demo per feature, new installation pages per bundler and framework, an [Attribution](/docs/attribution) page and a [Showcase](/showcase). +* Site-wide choice of synthesizer or backing track and of the SoundFont, and public-domain demo songs. +* The site builds and starts its dev server several times faster; nightlies deploy to [next.alphatab.net](https://next.alphatab.net). ### 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)) +* importer: Capella (`.capx`) files load instead of failing with "No compatible importer found for file" ([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: [`BoundsLookup.findBeats`](/docs/reference/types/rendering/boundslookup/#findbeats) returns each beat once in the horizontal layout, errors while the worker reads a score reach the [`error`](/docs/reference/api/error) event, and the JSON converter accepts `null` colors and fonts ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) +* platform: fixes for the IntersectionObserver fallback, zero-width rectangles on .NET and Android, and number parsing and collections on .NET and Kotlin ([#2997](https://github.com/CoderLine/alphaTab/pull/2997)) ### Upgrade Notes @@ -420,6 +463,7 @@ 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 rests, voice colors, triplet feel and MusicXML dynamics render differently ([#2997](https://github.com/CoderLine/alphaTab/pull/2997), [#2992](https://github.com/CoderLine/alphaTab/pull/2992), [#2996](https://github.com/CoderLine/alphaTab/pull/2996)); see the items above. #### Changed defaults @@ -441,16 +485,22 @@ 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 `