Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 6 additions & 20 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
32 changes: 0 additions & 32 deletions .github/workflows/website.yml

This file was deleted.

21 changes: 6 additions & 15 deletions .github/workflows/~publish_website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions packages/alphatab/src/importer/CapellaParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ export class CapellaParser {
}

private _voiceStates: Map<string, CapellaVoiceState> = new Map();
private _currentVoiceState!: CapellaVoiceState;
private _currentVoiceState: CapellaVoiceState = new CapellaVoiceState();
private _currentBar!: Bar;
private _currentVoice!: Voice;

Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down
Binary file not shown.
90 changes: 90 additions & 0 deletions packages/alphatab/test/importer/CapellaImporter.test.ts
Original file line number Diff line number Diff line change
@@ -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 = `<?xml version="1.0" encoding="UTF-8"?>
<score xmlns="http://www.capella.de/CapXML/3.0">
<info/>
<layout>
<staves>
<staffLayout description="Voice"><notation defaultClef="treble"/><sound instr="0" volume="100"/></staffLayout>
</staves>
</layout>
<systems>
<system>
<staves>
<staff layout="Voice" defaultTime="4/4">
<voices><voice><noteObjects>${noteObjects}</noteObjects></voice></voices>
</staff>
</staves>
</system>
</systems>
</score>`;
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(`
<chord><duration base="1/1"/><heads><head pitch="C5"/></heads></chord>
<barline type="single"/>
<rest><duration base="1/1"/></rest>
<barline type="single"/>
<rest><duration base="1"/></rest>
<barline type="single"/>
<chord><duration base="1/2"/><heads><head pitch="E5"/></heads></chord>
<rest><duration base="1/2"/></rest>
<barline type="end"/>`);

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]);
});
});
11 changes: 6 additions & 5 deletions website/docs/alphatex/syntax.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -55,10 +56,10 @@ Examples:

## Argument lists {#argument-lists}

<a id="argument-list"></a>
<Link id="argument-list" />
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.

<a id="argument"></a><a id="parameter"></a>
<Link id="argument" /><Link id="parameter" />
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:
Expand All @@ -83,7 +84,7 @@ Argument entries are separated by whitespace. Commas are not used.

## Properties {#properties}

<a id="property"></a>
<Link id="property" />
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).
Expand All @@ -96,7 +97,7 @@ C4 { b (0 4) v txt "Bend with Vibrato" }

## Metadata tags {#metadata-tags}

<a id="metadata-tag"></a>
<Link id="metadata-tag" />
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.
Expand Down Expand Up @@ -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 id="overload"></a>
<Link id="overload" />
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}
Expand Down
6 changes: 2 additions & 4 deletions website/docs/features/import-load/capella.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:

<AlphaTab tex={false} file="/files/features/Chords.gp5" />
<AlphaTab tex={false} file="/files/twinkle-mozart.capx" />

## Scope of the Capella importer

Expand Down
2 changes: 1 addition & 1 deletion website/docs/guides/exporter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion website/docs/releases/release1_7.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading