diff --git a/package.json b/package.json index 07c63bbc9..f9fdfd05f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,9 @@ ".": { "import": "./dist/alphaTab.mjs", "require": "./dist/alphaTab.js" - } + }, + "./soundfont/*": "./dist/soundfont/*", + "./font/*": "./dist/font/*" }, "engines": { "node": ">=6.0.0" diff --git a/src.compiler/BuilderHelpers.ts b/src.compiler/BuilderHelpers.ts index 2aebd0c6a..0e5258733 100644 --- a/src.compiler/BuilderHelpers.ts +++ b/src.compiler/BuilderHelpers.ts @@ -50,15 +50,21 @@ function findNode(node: ts.Node, kind: ts.SyntaxKind): ts.Node | null { export function addNewLines(stmts: ts.Statement[]) { return stmts.map(stmt => ts.addSyntheticTrailingComment(stmt, ts.SyntaxKind.SingleLineCommentTrivia, '', true)); } -export function getTypeWithNullableInfo(checker: ts.TypeChecker, node: ts.TypeNode | undefined) { +export function getTypeWithNullableInfo( + checker: ts.TypeChecker, + node: ts.TypeNode | undefined, + allowUnionAsPrimitive: boolean +) { if (!node) { return { isNullable: false, + isUnionType: false, type: {} as ts.Type }; } let isNullable = false; + let isUnionType = false; let type: ts.Type | null = null; if (ts.isUnionTypeNode(node)) { for (const t of node.types) { @@ -67,12 +73,18 @@ export function getTypeWithNullableInfo(checker: ts.TypeChecker, node: ts.TypeNo } else if (ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword) { isNullable = true; } else if (type !== null) { - throw new Error( - 'Multi union types on JSON settings not supported: ' + - node.getSourceFile().fileName + - ':' + - node.getText() - ); + if (allowUnionAsPrimitive) { + isUnionType = true; + type = checker.getTypeAtLocation(node); + break; + } else { + throw new Error( + 'Multi union types on JSON settings not supported: ' + + node.getSourceFile().fileName + + ':' + + node.getText() + ); + } } else { type = checker.getTypeAtLocation(t); } @@ -83,6 +95,7 @@ export function getTypeWithNullableInfo(checker: ts.TypeChecker, node: ts.TypeNo return { isNullable, + isUnionType, type: type as ts.Type }; } @@ -98,6 +111,9 @@ export function unwrapArrayItemType(type: ts.Type, typeChecker: ts.TypeChecker): if (type.isUnion()) { const nonNullable = typeChecker.getNonNullableType(type); + if (type === nonNullable) { + return null; + } return unwrapArrayItemType(nonNullable, typeChecker); } @@ -170,7 +186,7 @@ export function isMap(type: ts.Type | null): boolean { } function markNodeSynthesized(node: ts.Node): ts.Node { - for(const c of node.getChildren()) { + for (const c of node.getChildren()) { markNodeSynthesized(c); } ts.setTextRange(node, { diff --git a/src.compiler/typescript/CloneEmitter.ts b/src.compiler/typescript/CloneEmitter.ts index 01ea069fc..1a0d5fb93 100644 --- a/src.compiler/typescript/CloneEmitter.ts +++ b/src.compiler/typescript/CloneEmitter.ts @@ -56,7 +56,7 @@ function generateClonePropertyStatements( typeChecker: ts.TypeChecker, importer: (name: string, module: string) => void ): ts.Statement[] { - const propertyType = getTypeWithNullableInfo(typeChecker, prop.type!); + const propertyType = getTypeWithNullableInfo(typeChecker, prop.type!, true); const statements: ts.Statement[] = []; diff --git a/src.compiler/typescript/Serializer.common.ts b/src.compiler/typescript/Serializer.common.ts index 61733e49f..f45aed9de 100644 --- a/src.compiler/typescript/Serializer.common.ts +++ b/src.compiler/typescript/Serializer.common.ts @@ -6,6 +6,7 @@ export interface JsonProperty { property: ts.PropertyDeclaration; jsonNames: string[]; target?: string; + isReadOnly: boolean; } export interface JsonSerializable { diff --git a/src.compiler/typescript/Serializer.setProperty.ts b/src.compiler/typescript/Serializer.setProperty.ts index 007340da0..d2263914d 100644 --- a/src.compiler/typescript/Serializer.setProperty.ts +++ b/src.compiler/typescript/Serializer.setProperty.ts @@ -66,9 +66,9 @@ function isPrimitiveFromJson(type: ts.Type, typeChecker: ts.TypeChecker) { return null; } -function cloneTypeNode(node: ts.TypeNode): ts.TypeNode { +function cloneTypeNode(node: T): T { if (ts.isUnionTypeNode(node)) { - return ts.factory.createUnionTypeNode(node.types.map(cloneTypeNode)); + return ts.factory.createUnionTypeNode(node.types.map(cloneTypeNode)) as any as T; } else if ( node.kind === ts.SyntaxKind.StringKeyword || node.kind === ts.SyntaxKind.NumberKeyword || @@ -77,11 +77,21 @@ function cloneTypeNode(node: ts.TypeNode): ts.TypeNode { node.kind === ts.SyntaxKind.AnyKeyword || node.kind === ts.SyntaxKind.VoidKeyword ) { - return ts.factory.createKeywordTypeNode(node.kind); + return ts.factory.createKeywordTypeNode(node.kind) as any as T; } else if (ts.isLiteralTypeNode(node)) { - return ts.factory.createLiteralTypeNode(node.literal); + return ts.factory.createLiteralTypeNode(node.literal) as any as T; } else if (ts.isArrayTypeNode(node)) { - return ts.factory.createArrayTypeNode(cloneTypeNode(node.elementType)); + return ts.factory.createArrayTypeNode(cloneTypeNode(node.elementType)) as any as T; + } else if (ts.isTypeReferenceNode(node)) { + return ts.factory.createTypeReferenceNode(cloneTypeNode(node.typeName)) as any as T; + } else if (ts.isIdentifier(node)) { + return ts.factory.createIdentifier(node.text) as any as T; + } else if (ts.isQualifiedName(node)) { + if (typeof node.right === 'string') { + return ts.factory.createQualifiedName(cloneTypeNode(node.left), node.right) as any as T; + } else { + return ts.factory.createQualifiedName(cloneTypeNode(node.left), cloneTypeNode(node.right)) as any as T; + } } throw new Error(`Unsupported TypeNode: '${ts.SyntaxKind[node.kind]}' extend type node cloning`); @@ -103,7 +113,7 @@ function generateSetPropertyBody( const caseStatements: ts.Statement[] = []; - const type = getTypeWithNullableInfo(typeChecker, prop.property.type); + const type = getTypeWithNullableInfo(typeChecker, prop.property.type, true); const assignField = function (expr: ts.Expression): ts.Statement { return ts.factory.createExpressionStatement( @@ -114,7 +124,19 @@ function generateSetPropertyBody( ); }; - if (isPrimitiveFromJson(type.type!, typeChecker)) { + if (type.isUnionType) { + caseStatements.push( + assignField( + ts.factory.createAsExpression( + type.isNullable + ? ts.factory.createIdentifier('v') + : ts.factory.createNonNullExpression(ts.factory.createIdentifier('v')), + cloneTypeNode(prop.property.type!) + ) + ) + ); + caseStatements.push(ts.factory.createReturnStatement(ts.factory.createTrue())); + } else if (isPrimitiveFromJson(type.type!, typeChecker)) { caseStatements.push( assignField( ts.factory.createAsExpression( @@ -203,7 +225,8 @@ function generateSetPropertyBody( ); importer('JsonHelper', '@src/io/JsonHelper'); mapKey = createNodeFromSource( - `JsonHelper.parseEnum<${mapType.typeArguments![0].symbol!.name}>(k, ${mapType.typeArguments![0].symbol!.name + `JsonHelper.parseEnum<${mapType.typeArguments![0].symbol!.name}>(k, ${ + mapType.typeArguments![0].symbol!.name })!`, ts.SyntaxKind.NonNullExpression ); @@ -232,10 +255,11 @@ function generateSetPropertyBody( mapValue = ts.factory.createIdentifier('i'); } - const collectionAddMethod = ts - .getJSDocTags(prop.property) - .filter(t => t.tagName.text === 'json_add') - .map(t => t.comment ?? '')[0] as string || fieldName + '.set'; + const collectionAddMethod = + (ts + .getJSDocTags(prop.property) + .filter(t => t.tagName.text === 'json_add') + .map(t => t.comment ?? '')[0] as string) || fieldName + '.set'; caseStatements.push( assignField( @@ -270,29 +294,29 @@ function generateSetPropertyBody( addNewLines( [ itemSerializer.length > 0 && - createNodeFromSource( - `const i = new ${mapType.typeArguments![1].symbol.name}();`, - ts.SyntaxKind.VariableStatement - ), + createNodeFromSource( + `const i = new ${mapType.typeArguments![1].symbol.name}();`, + ts.SyntaxKind.VariableStatement + ), itemSerializer.length > 0 && - createNodeFromSource( - `${itemSerializer}.fromJson(i, v as Map)`, - ts.SyntaxKind.ExpressionStatement - ), + createNodeFromSource( + `${itemSerializer}.fromJson(i, v as Map)`, + ts.SyntaxKind.ExpressionStatement + ), ts.factory.createExpressionStatement( ts.factory.createCallExpression( collectionAddMethod ? ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('obj'), - collectionAddMethod - ) + ts.factory.createIdentifier('obj'), + collectionAddMethod + ) : ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('obj'), - ts.factory.createIdentifier(fieldName) - ), - ts.factory.createIdentifier('set') - ), + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('obj'), + ts.factory.createIdentifier(fieldName) + ), + ts.factory.createIdentifier('set') + ), undefined, [mapKey, mapValue] ) @@ -320,10 +344,7 @@ function generateSetPropertyBody( ) ); caseStatements.push( - createNodeFromSource( - `return true;`, - ts.SyntaxKind.ReturnStatement - ) + createNodeFromSource(`return true;`, ts.SyntaxKind.ReturnStatement) ); } else { // for complex types it is a bit more tricky @@ -355,156 +376,156 @@ function generateSetPropertyBody( ts.factory.createBlock( !type.isNullable ? [ - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - // TypeName.fromJson - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(itemSerializer), - 'fromJson' - ), - [], - [ - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('obj'), - fieldName - ), - ts.factory.createAsExpression( - ts.factory.createIdentifier('v'), - createStringUnknownMapNode() - ) - ] - ) - ), - ts.factory.createReturnStatement(ts.factory.createTrue()) - ] + ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + // TypeName.fromJson + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(itemSerializer), + 'fromJson' + ), + [], + [ + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('obj'), + fieldName + ), + ts.factory.createAsExpression( + ts.factory.createIdentifier('v'), + createStringUnknownMapNode() + ) + ] + ) + ), + ts.factory.createReturnStatement(ts.factory.createTrue()) + ] : [ - ts.factory.createIfStatement( - ts.factory.createIdentifier('v'), - ts.factory.createBlock([ - assignField( - ts.factory.createNewExpression( - ts.factory.createIdentifier(type.type.symbol.name), - undefined, - [] - ) - ), - ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - // TypeName.fromJson - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(itemSerializer), - 'fromJson' - ), - [], - [ - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('obj'), - fieldName - ), - ts.factory.createAsExpression( - ts.factory.createIdentifier('v'), - createStringUnknownMapNode() - ) - ] - ) - ) - ]), - ts.factory.createBlock([assignField(ts.factory.createNull())]) - ), - ts.factory.createReturnStatement(ts.factory.createTrue()) - ] + ts.factory.createIfStatement( + ts.factory.createIdentifier('v'), + ts.factory.createBlock([ + assignField( + ts.factory.createNewExpression( + ts.factory.createIdentifier(type.type.symbol.name), + undefined, + [] + ) + ), + ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + // TypeName.fromJson + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(itemSerializer), + 'fromJson' + ), + [], + [ + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('obj'), + fieldName + ), + ts.factory.createAsExpression( + ts.factory.createIdentifier('v'), + createStringUnknownMapNode() + ) + ] + ) + ) + ]), + ts.factory.createBlock([assignField(ts.factory.createNull())]) + ), + ts.factory.createReturnStatement(ts.factory.createTrue()) + ] ), !prop.partialNames ? undefined : ts.factory.createBlock([ - // for(const candidate of ["", "core"]) { - // if(candidate.indexOf(property) === 0) { - // if(!this.field) { this.field = new FieldType(); } - // if(this.field.setProperty(property.substring(candidate.length), value)) return true; - // } - // } - ts.factory.createForOfStatement( - undefined, - ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration('c')], - ts.NodeFlags.Const - ), - jsonNameArray, - ts.factory.createBlock([ - ts.factory.createIfStatement( - ts.factory.createBinaryExpression( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('property'), - 'indexOf' - ), - [], - [ts.factory.createIdentifier('c')] - ), - ts.SyntaxKind.EqualsEqualsEqualsToken, - ts.factory.createNumericLiteral('0') - ), - ts.factory.createBlock( - [ - type.isNullable && - ts.factory.createIfStatement( - ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.ExclamationToken, - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('obj'), - fieldName - ) - ), - ts.factory.createBlock([ - assignField( - ts.factory.createNewExpression( - ts.factory.createIdentifier( - type.type!.symbol!.name - ), - [], - [] - ) - ) - ]) - ), - ts.factory.createIfStatement( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(itemSerializer), - 'setProperty' - ), - [], - [ - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('obj'), - fieldName - ), - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('property'), - 'substring' - ), - [], - [ - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('c'), - 'length' - ) - ] - ), - ts.factory.createIdentifier('v') - ] - ), - ts.factory.createBlock([ - ts.factory.createReturnStatement(ts.factory.createTrue()) - ]) - ) - ].filter(s => !!s) as ts.Statement[] - ) - ) - ]) - ) - ]) + // for(const candidate of ["", "core"]) { + // if(candidate.indexOf(property) === 0) { + // if(!this.field) { this.field = new FieldType(); } + // if(this.field.setProperty(property.substring(candidate.length), value)) return true; + // } + // } + ts.factory.createForOfStatement( + undefined, + ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration('c')], + ts.NodeFlags.Const + ), + jsonNameArray, + ts.factory.createBlock([ + ts.factory.createIfStatement( + ts.factory.createBinaryExpression( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('property'), + 'indexOf' + ), + [], + [ts.factory.createIdentifier('c')] + ), + ts.SyntaxKind.EqualsEqualsEqualsToken, + ts.factory.createNumericLiteral('0') + ), + ts.factory.createBlock( + [ + type.isNullable && + ts.factory.createIfStatement( + ts.factory.createPrefixUnaryExpression( + ts.SyntaxKind.ExclamationToken, + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('obj'), + fieldName + ) + ), + ts.factory.createBlock([ + assignField( + ts.factory.createNewExpression( + ts.factory.createIdentifier( + type.type!.symbol!.name + ), + [], + [] + ) + ) + ]) + ), + ts.factory.createIfStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(itemSerializer), + 'setProperty' + ), + [], + [ + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('obj'), + fieldName + ), + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('property'), + 'substring' + ), + [], + [ + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('c'), + 'length' + ) + ] + ), + ts.factory.createIdentifier('v') + ] + ), + ts.factory.createBlock([ + ts.factory.createReturnStatement(ts.factory.createTrue()) + ]) + ) + ].filter(s => !!s) as ts.Statement[] + ) + ) + ]) + ) + ]) ) ); } @@ -537,19 +558,16 @@ function generateSetPropertyBody( statements.unshift(switchExpr); } - if(serializable.hasSetPropertyExtension) { - statements.push(ts.factory.createReturnStatement( - createNodeFromSource( - `obj.setProperty(property, v);`, - ts.SyntaxKind.CallExpression + if (serializable.hasSetPropertyExtension) { + statements.push( + ts.factory.createReturnStatement( + createNodeFromSource(`obj.setProperty(property, v);`, ts.SyntaxKind.CallExpression) ) - )); - } - else { + ); + } else { statements.push(ts.factory.createReturnStatement(ts.factory.createFalse())); } - return ts.factory.createBlock(addNewLines(statements)); } diff --git a/src.compiler/typescript/Serializer.toJson.ts b/src.compiler/typescript/Serializer.toJson.ts index 40a4757e9..a3d643b68 100644 --- a/src.compiler/typescript/Serializer.toJson.ts +++ b/src.compiler/typescript/Serializer.toJson.ts @@ -92,11 +92,11 @@ function generateToJsonBody( const fieldName = (prop.property.name as ts.Identifier).text; const jsonName = prop.jsonNames.filter(n => n !== '')[0]; - if (!jsonName) { + if (!jsonName || prop.isReadOnly) { continue; } const typeChecker = program.getTypeChecker(); - const type = getTypeWithNullableInfo(typeChecker, prop.property.type!); + const type = getTypeWithNullableInfo(typeChecker, prop.property.type!, false); const isArray = isTypedArray(type.type!); let propertyStatements: ts.Statement[] = []; diff --git a/src.compiler/typescript/SerializerEmitter.ts b/src.compiler/typescript/SerializerEmitter.ts index a3492d89d..cad7c1c1c 100644 --- a/src.compiler/typescript/SerializerEmitter.ts +++ b/src.compiler/typescript/SerializerEmitter.ts @@ -45,7 +45,8 @@ export default createEmitter('json', (program, input) => { property: propertyDeclaration, jsonNames: jsonNames, partialNames: !!ts.getJSDocTags(member).find(t => t.tagName.text === 'json_partial_names'), - target: ts.getJSDocTags(member).find(t => t.tagName.text === 'target')?.comment as string + target: ts.getJSDocTags(member).find(t => t.tagName.text === 'target')?.comment as string, + isReadOnly: !!ts.getJSDocTags(member).find(t => t.tagName.text === 'json_read_only') }); } } diff --git a/src/PlayerSettings.ts b/src/PlayerSettings.ts index 72d261c81..a2336771d 100644 --- a/src/PlayerSettings.ts +++ b/src/PlayerSettings.ts @@ -102,8 +102,10 @@ export class PlayerSettings { /** * Gets or sets the element that should be used for scrolling. + * @target web + * @json_read_only */ - public scrollElement: string = 'html,body'; + public scrollElement: string | HTMLElement = 'html,body'; /** * Gets or sets whether the player should be enabled. diff --git a/src/generated/PlayerSettingsSerializer.ts b/src/generated/PlayerSettingsSerializer.ts index 54d338ebe..ae70130dc 100644 --- a/src/generated/PlayerSettingsSerializer.ts +++ b/src/generated/PlayerSettingsSerializer.ts @@ -21,7 +21,6 @@ export class PlayerSettingsSerializer { } const o = new Map(); o.set("soundfont", obj.soundFont); - o.set("scrollelement", obj.scrollElement); o.set("enableplayer", obj.enablePlayer); o.set("enablecursor", obj.enableCursor); o.set("enableanimatedbeatcursor", obj.enableAnimatedBeatCursor); @@ -45,8 +44,9 @@ export class PlayerSettingsSerializer { case "soundfont": obj.soundFont = v as string | null; return true; + /*@target web*/ case "scrollelement": - obj.scrollElement = v! as string; + obj.scrollElement = v! as string | HTMLElement; return true; case "enableplayer": obj.enablePlayer = v! as boolean; diff --git a/src/platform/javascript/AlphaTabApi.ts b/src/platform/javascript/AlphaTabApi.ts index 8a9adaeae..01082b573 100644 --- a/src/platform/javascript/AlphaTabApi.ts +++ b/src/platform/javascript/AlphaTabApi.ts @@ -15,8 +15,8 @@ import { SettingsSerializer } from '@src/generated/SettingsSerializer'; /** * @target web */ -export class AlphaTabApi extends AlphaTabApiBase { - public constructor(element: HTMLElement, options: unknown) { +export class AlphaTabApi extends AlphaTabApiBase { + public constructor(element: HTMLElement, options: any|Settings) { super(new BrowserUiFacade(element), options); } @@ -25,7 +25,7 @@ export class AlphaTabApi extends AlphaTabApiBase { super.tex(tex, browser.parseTracks(tracks)); } - public print(width: string, additionalSettings:unknown = null): void { + public print(width?: string, additionalSettings:unknown = null): void { // prepare a popup window for printing (a4 width, window height, centered) let preview: Window = window.open('', '', 'width=0,height=0')!; let a4: HTMLElement = preview.document.createElement('div'); diff --git a/src/platform/javascript/BrowserUiFacade.ts b/src/platform/javascript/BrowserUiFacade.ts index 92b84fd23..3977abac4 100644 --- a/src/platform/javascript/BrowserUiFacade.ts +++ b/src/platform/javascript/BrowserUiFacade.ts @@ -159,7 +159,7 @@ export class BrowserUiFacade implements IUiFacade { return new AlphaTabWorkerScoreRenderer(this._api, this._api.settings); } - public initialize(api: AlphaTabApiBase, raw: unknown): void { + public initialize(api: AlphaTabApiBase, raw: any | Settings): void { this._api = api; let settings: Settings; if (raw instanceof Settings) { @@ -432,8 +432,8 @@ export class BrowserUiFacade implements IUiFacade { placeholder.replaceChildren(body as Node); } placeholder.resultState = ResultState.RenderDone; - placeholder.renderedResultId = renderResult.id; - placeholder.renderedResult = Array.from(placeholder.children) + placeholder.renderedResultId = renderResult.id; + placeholder.renderedResult = Array.from(placeholder.children); } public beginAppendRenderResults(renderResult: RenderFinishedEventArgs | null): void { @@ -501,7 +501,8 @@ export class BrowserUiFacade implements IUiFacade { // Once https://github.com/webpack/webpack/issues/11543 is decided // we can support audio worklets together with WebPack - let supportsAudioWorklets: boolean = window.isSecureContext && 'AudioWorkletNode' in window && !Environment.isWebPackBundled; + let supportsAudioWorklets: boolean = + window.isSecureContext && 'AudioWorkletNode' in window && !Environment.isWebPackBundled; if (supportsAudioWorklets) { Logger.debug('Player', 'Will use webworkers for synthesizing and web audio api with worklets for playback'); diff --git a/test/model/JsonConverter.test.ts b/test/model/JsonConverter.test.ts index 8f22ccb9f..75223ba79 100644 --- a/test/model/JsonConverter.test.ts +++ b/test/model/JsonConverter.test.ts @@ -128,6 +128,7 @@ describe('JsonConverterTest', () => { expected.importer.mergePartGroupsInMusicXml = false; expected.player.soundFont = 'soundfont'; + /**@target web*/ expected.player.scrollElement = 'scroll'; expected.player.vibrato.noteSlightAmplitude = 10; expected.player.slide.simpleSlideDurationRatio = 8;