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
13 changes: 12 additions & 1 deletion packages/alphatab/scripts/Serializer.toJson.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as ts from 'typescript';
import { createNodeFromSource, setMethodBody } from '@coderline/alphatab-transpiler/src/BuilderHelpers';
import * as ts from 'typescript';
import { findSerializerModule } from './Serializer.common';
import type { TypeSchema } from './TypeSchema';

Expand Down Expand Up @@ -153,6 +153,17 @@ function generateToJsonBody(serializable: TypeSchema, importer: (name: string, m
}`,
ts.SyntaxKind.Block
);
} else if(prop.type.typeArguments![1].isArray && prop.type.typeArguments![1].arrayItemType!.isPrimitiveType) {
serializeBlock = createNodeFromSource<ts.Block>(
`{
const m = new Map<string, unknown>();
o.set(${JSON.stringify(jsonName)}, m);
for(const [k, v] of obj.${fieldName}!) {
m.set(k.toString(), v);
}
}`,
ts.SyntaxKind.Block
);
} else {
const itemSerializer = `${prop.type.typeArguments![1].typeAsString}Serializer`;
importer(itemSerializer, findSerializerModule(prop.type.typeArguments![1]));
Expand Down
37 changes: 37 additions & 0 deletions packages/alphatab/src/exporter/GpifWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,10 @@ export class GpifWriter {
masterBarNode.addElement('Time').innerText =
`${masterBar.timeSignatureNumerator}/${masterBar.timeSignatureDenominator}`;

if (masterBar.actualBeamingRules) {
this._writeBarXProperties(masterBarNode, masterBar);
}

if (masterBar.isFreeTime) {
masterBarNode.addElement('FreeTime');
}
Expand Down Expand Up @@ -1739,6 +1743,39 @@ export class GpifWriter {
this._writeFermatas(masterBarNode, masterBar);
}

private _writeBarXProperties(masterBarNode: XmlNode, masterBar: MasterBar) {
const properties = masterBarNode.addElement('XProperties');

const beamingRules = masterBar.actualBeamingRules;
if (beamingRules) {
// prefer 8th note rule (that's what GP mostly has)
const rule = beamingRules!.findRule(Duration.Eighth);

// NOTE: it's not clear if guitar pro supports quarter rules
// for that case we better convert this to an "8th" note rule.
let durationProp = rule[0] as number;
let groupSizeFactor = 1;
if (rule[0] === Duration.Quarter) {
durationProp = 8;
groupSizeFactor = 2;
}

this._writeSimpleXPropertyNode(properties, '1124139010', 'Int', durationProp.toString());

const startGroupid = 1124139264;
let i = 0;
while (startGroupid < 1124139295 && i < rule[1].length) {
this._writeSimpleXPropertyNode(
properties,
(startGroupid + i).toString(),
'Int',
(rule[1][i] * groupSizeFactor).toString()
);
i++;
}
}
}

private _writeFermatas(parent: XmlNode, masterBar: MasterBar) {
const fermataCount = masterBar.fermata?.size ?? 0;
if (fermataCount === 0) {
Expand Down
44 changes: 44 additions & 0 deletions packages/alphatab/src/generated/model/BeamingRulesSerializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// <auto-generated>
// This code was auto-generated.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
import { BeamingRules } from "@coderline/alphatab/model/MasterBar";
import { JsonHelper } from "@coderline/alphatab/io/JsonHelper";
import { Duration } from "@coderline/alphatab/model/Duration";
/**
* @internal
*/
export class BeamingRulesSerializer {
public static fromJson(obj: BeamingRules, m: unknown): void {
if (!m) {
return;
}
JsonHelper.forEach(m, (v, k) => BeamingRulesSerializer.setProperty(obj, k, v));
}
public static toJson(obj: BeamingRules | null): Map<string, unknown> | null {
if (!obj) {
return null;
}
const o = new Map<string, unknown>();
{
const m = new Map<string, unknown>();
o.set("groups", m);
for (const [k, v] of obj.groups!) {
m.set(k.toString(), v);
}
}
return o;
}
public static setProperty(obj: BeamingRules, property: string, v: unknown): boolean {
switch (property) {
case "groups":
obj.groups = new Map<Duration, number[]>();
JsonHelper.forEach(v, (v, k) => {
obj.groups.set(JsonHelper.parseEnum<Duration>(k, Duration)!, v as number[]);
});
return true;
}
return false;
}
}
14 changes: 14 additions & 0 deletions packages/alphatab/src/generated/model/MasterBarSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
// </auto-generated>
import { MasterBar } from "@coderline/alphatab/model/MasterBar";
import { JsonHelper } from "@coderline/alphatab/io/JsonHelper";
import { BeamingRulesSerializer } from "@coderline/alphatab/generated/model/BeamingRulesSerializer";
import { SectionSerializer } from "@coderline/alphatab/generated/model/SectionSerializer";
import { AutomationSerializer } from "@coderline/alphatab/generated/model/AutomationSerializer";
import { FermataSerializer } from "@coderline/alphatab/generated/model/FermataSerializer";
import { BeamingRules } from "@coderline/alphatab/model/MasterBar";
import { TripletFeel } from "@coderline/alphatab/model/TripletFeel";
import { Section } from "@coderline/alphatab/model/Section";
import { Automation } from "@coderline/alphatab/model/Automation";
Expand Down Expand Up @@ -35,6 +37,9 @@ export class MasterBarSerializer {
o.set("timesignaturenumerator", obj.timeSignatureNumerator);
o.set("timesignaturedenominator", obj.timeSignatureDenominator);
o.set("timesignaturecommon", obj.timeSignatureCommon);
if (obj.beamingRules) {
o.set("beamingrules", BeamingRulesSerializer.toJson(obj.beamingRules));
}
o.set("isfreetime", obj.isFreeTime);
o.set("tripletfeel", obj.tripletFeel as number);
if (obj.section) {
Expand Down Expand Up @@ -87,6 +92,15 @@ export class MasterBarSerializer {
case "timesignaturecommon":
obj.timeSignatureCommon = v! as boolean;
return true;
case "beamingrules":
if (v) {
obj.beamingRules = new BeamingRules();
BeamingRulesSerializer.fromJson(obj.beamingRules, v);
}
else {
obj.beamingRules = undefined;
}
return true;
case "isfreetime":
obj.isFreeTime = v! as boolean;
return true;
Expand Down
35 changes: 34 additions & 1 deletion packages/alphatab/src/importer/GpifParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { HarmonicType } from '@coderline/alphatab/model/HarmonicType';
import { KeySignature } from '@coderline/alphatab/model/KeySignature';
import { KeySignatureType } from '@coderline/alphatab/model/KeySignatureType';
import { Lyrics } from '@coderline/alphatab/model/Lyrics';
import { MasterBar } from '@coderline/alphatab/model/MasterBar';
import { BeamingRules, MasterBar } from '@coderline/alphatab/model/MasterBar';
import { Note } from '@coderline/alphatab/model/Note';
import { Ottavia } from '@coderline/alphatab/model/Ottavia';
import { PickStroke } from '@coderline/alphatab/model/PickStroke';
Expand Down Expand Up @@ -1983,6 +1983,9 @@ export class GpifParser {
}

private _parseMasterBarXProperties(masterBar: MasterBar, node: XmlNode) {
let beamingRuleDuration: number = Number.NaN;
let beamingRuleGroups: number[] | undefined = undefined;

for (const c of node.childElements()) {
switch (c.localName) {
case 'XProperty':
Expand All @@ -1994,10 +1997,40 @@ export class GpifParser {
1
);
break;
case '1124139010':
beamingRuleDuration = GpifParser._parseIntSafe(
c.findChildElement('Int')?.innerText,
Number.NaN
);
break;
default:
const idNumeric = GpifParser._parseIntSafe(id, 0);
if (idNumeric >= 1124139264 && idNumeric <= 1124139295) {
const groupIndex = idNumeric - 1124139264;
const groupSize = GpifParser._parseIntSafe(
c.findChildElement('Int')?.innerText,
Number.NaN
);

if (beamingRuleGroups === undefined) {
beamingRuleGroups = [];
}
while (beamingRuleGroups.length < groupIndex + 1) {
beamingRuleGroups.push(0);
}
beamingRuleGroups[groupIndex] = groupSize;
}
break;
}
break;
}
}

if (!Number.isNaN(beamingRuleDuration) && beamingRuleGroups) {
const rules = new BeamingRules();
rules.groups.set(beamingRuleDuration as Duration, beamingRuleGroups);
masterBar.beamingRules = rules;
}
}

private _parseBeatProperties(node: XmlNode, beat: Beat): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,16 @@ export class AlphaTex1LanguageDefinitions {
['spu', [[[[16], 2]]]],
['db', null],
['voicemode', [[[[10, 17], 0, ['staffwise', 'barwise']]]]],
['barnumberdisplay', [[[[10, 17], 0, ['allbars', 'firstofsystem', 'hide']]]]]
['barnumberdisplay', [[[[10, 17], 0, ['allbars', 'firstofsystem', 'hide']]]]],
[
'beaming',
[
[
[[16], 0],
[[16], 5]
]
]
]
]);
public static readonly metaDataProperties = AlphaTex1LanguageDefinitions._metaProps([
[
Expand Down Expand Up @@ -596,7 +605,8 @@ export class AlphaTex1LanguageDefinitions {
['spu', null],
['db', null],
['voicemode', null],
['barnumberdisplay', null]
['barnumberdisplay', null],
['beaming', null]
]);
public static readonly metaDataSignatures = [
AlphaTex1LanguageDefinitions.scoreMetaDataSignatures,
Expand Down
107 changes: 100 additions & 7 deletions packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import { GraceType } from '@coderline/alphatab/model/GraceType';
import { HarmonicType } from '@coderline/alphatab/model/HarmonicType';
import { KeySignatureType } from '@coderline/alphatab/model/KeySignatureType';
import { Lyrics } from '@coderline/alphatab/model/Lyrics';
import type { MasterBar } from '@coderline/alphatab/model/MasterBar';
import { BeamingRules, type MasterBar } from '@coderline/alphatab/model/MasterBar';
import { ModelUtils } from '@coderline/alphatab/model/ModelUtils';
import type { Note } from '@coderline/alphatab/model/Note';
import { NoteAccidentalMode } from '@coderline/alphatab/model/NoteAccidentalMode';
Expand Down Expand Up @@ -92,6 +92,8 @@ import { SynthConstants } from '@coderline/alphatab/synth/SynthConstants';
export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler {
public static readonly instance = new AlphaTex1LanguageHandler();

private static readonly _timeSignatureDenominators = new Set<number>([1, 2, 4, 8, 16, 32, 64, 128]);

public applyScoreMetaData(
importer: IAlphaTexImporter,
score: Score,
Expand Down Expand Up @@ -597,12 +599,39 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler
case 'ts':
switch (metaData.arguments!.arguments[0].nodeType) {
case AlphaTexNodeType.Number:
bar.masterBar.timeSignatureNumerator = (
metaData.arguments!.arguments[0] as AlphaTexNumberLiteral
).value;
bar.masterBar.timeSignatureDenominator = (
metaData.arguments!.arguments[1] as AlphaTexNumberLiteral
).value;
bar.masterBar.timeSignatureNumerator =
(metaData.arguments!.arguments[0] as AlphaTexNumberLiteral).value | 0;

if (bar.masterBar.timeSignatureNumerator < 1 || bar.masterBar.timeSignatureNumerator > 32) {
importer.addSemanticDiagnostic({
code: AlphaTexDiagnosticCode.AT211,
message: `Value is out of valid range. Allowed range: 1-32, Actual Value: ${bar.masterBar.timeSignatureNumerator}`,
start: metaData.arguments!.arguments[0].start,
end: metaData.arguments!.arguments[0].end,
severity: AlphaTexDiagnosticsSeverity.Error
});
}

bar.masterBar.timeSignatureDenominator =
(metaData.arguments!.arguments[1] as AlphaTexNumberLiteral).value | 0;

if (
!AlphaTex1LanguageHandler._timeSignatureDenominators.has(
bar.masterBar.timeSignatureDenominator
)
) {
const valueList = Array.from(AlphaTex1LanguageHandler._timeSignatureDenominators).join(
', '
);
importer.addSemanticDiagnostic({
code: AlphaTexDiagnosticCode.AT211,
message: `Value is out of valid range. Allowed range: ${valueList}, Actual Value: ${bar.masterBar.timeSignatureDenominator}`,
start: metaData.arguments!.arguments[0].start,
end: metaData.arguments!.arguments[0].end,
severity: AlphaTexDiagnosticsSeverity.Error
});
}

break;
case AlphaTexNodeType.Ident:
case AlphaTexNodeType.String:
Expand All @@ -624,6 +653,8 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler
break;
}
return ApplyNodeResult.Applied;
case 'beaming':
return this._parseBeamingRule(importer, metaData, bar.masterBar);
case 'ks':
const keySignature = AlphaTex1LanguageHandler._parseEnumValue(
importer,
Expand Down Expand Up @@ -884,6 +915,57 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler
}
}

private _parseBeamingRule(importer: IAlphaTexImporter, metaData: AlphaTexMetaDataNode, masterBar: MasterBar) {
let duration = Duration.Eighth;
const groupSizes: number[] = [];

const durationValue = (metaData.arguments!.arguments[0] as AlphaTexNumberLiteral).value;
switch (durationValue) {
case 4:
duration = Duration.QuadrupleWhole;
break;
case 8:
duration = Duration.Eighth;
break;
case 16:
duration = Duration.Sixteenth;
break;
case 32:
duration = Duration.ThirtySecond;
break;
default:
importer.addSemanticDiagnostic({
code: AlphaTexDiagnosticCode.AT209,
message: `Value is out of valid range. Allowed range: 4,8,16 or 32, Actual Value: ${durationValue}`,
severity: AlphaTexDiagnosticsSeverity.Error,
start: metaData.arguments!.arguments[0].start,
end: metaData.arguments!.arguments[0].end
});
return ApplyNodeResult.NotAppliedSemanticError;
}

for (let i = 1; i < metaData.arguments!.arguments.length; i++) {
const groupSize = (metaData.arguments!.arguments[i] as AlphaTexNumberLiteral).value;
if (groupSize < 1) {
importer.addSemanticDiagnostic({
code: AlphaTexDiagnosticCode.AT209,
message: `Value is out of valid range. Allowed range: >0, Actual Value: ${durationValue}`,
severity: AlphaTexDiagnosticsSeverity.Error,
start: metaData.arguments!.arguments[i].start,
end: metaData.arguments!.arguments[i].end
});
return ApplyNodeResult.NotAppliedSemanticError;
}
groupSizes.push((metaData.arguments!.arguments[i] as AlphaTexNumberLiteral).value);
}

if (!masterBar.beamingRules) {
masterBar.beamingRules = new BeamingRules();
}
masterBar.beamingRules!.groups.set(duration, groupSizes);
return ApplyNodeResult.Applied;
}

private static _handleAccidentalMode(importer: IAlphaTexImporter, args: AlphaTexArgumentList): ApplyNodeResult {
const accidentalMode = AlphaTex1LanguageHandler._parseEnumValue(
importer,
Expand Down Expand Up @@ -2888,6 +2970,17 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler
}
}

if (masterBar.beamingRules) {
for (const [k, v] of masterBar.beamingRules.groups) {
const args = Atnf.args([Atnf.number(k)], true);
for (const i of v) {
args!.arguments.push(Atnf.number(i));
}

nodes.push(Atnf.meta('beaming', args));
}
}

if (
(masterBar.index > 0 && masterBar.tripletFeel !== masterBar.previousMasterBar?.tripletFeel) ||
(masterBar.index === 0 && masterBar.tripletFeel !== TripletFeel.NoTripletFeel)
Expand Down
Loading