diff --git a/src.compiler/BuilderHelpers.ts b/src.compiler/BuilderHelpers.ts
index ead0b0e9f..2aebd0c6a 100644
--- a/src.compiler/BuilderHelpers.ts
+++ b/src.compiler/BuilderHelpers.ts
@@ -1,10 +1,57 @@
 import * as ts from 'typescript';
 
+export function setMethodBody(m: ts.MethodDeclaration, body: ts.FunctionBody): ts.MethodDeclaration {
+    return ts.factory.updateMethodDeclaration(
+        m,
+        m.decorators,
+        m.modifiers,
+        m.asteriskToken,
+        m.name,
+        m.questionToken,
+        m.typeParameters,
+        m.parameters,
+        m.type,
+        body
+    );
+}
+
+export function createNodeFromSource<T extends ts.Node>(source: string, kind: ts.SyntaxKind): T {
+    const sourceFile = ts.createSourceFile(
+        'temp.ts',
+        source.trim(),
+        ts.ScriptTarget.Latest,
+        /*setParentNodes */ true,
+        ts.ScriptKind.TS
+    );
+    const node = findNode(sourceFile, kind);
+    if (!node) {
+        throw new Error(
+            `Could not parse TS source to ${ts.SyntaxKind[kind]}, node count was ${sourceFile.getChildCount()}`
+        );
+    }
+    return markNodeSynthesized(node) as T;
+}
+
+function findNode(node: ts.Node, kind: ts.SyntaxKind): ts.Node | null {
+    if (node.kind === kind) {
+        return node;
+    }
+
+    for (const c of node.getChildren()) {
+        const f = findNode(c, kind);
+        if (f) {
+            return f;
+        }
+    }
+
+    return 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) {
-    if(!node) {
+    if (!node) {
         return {
             isNullable: false,
             type: {} as ts.Type
@@ -20,7 +67,12 @@ 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());
+                throw new Error(
+                    'Multi union types on JSON settings not supported: ' +
+                        node.getSourceFile().fileName +
+                        ':' +
+                        node.getText()
+                );
             } else {
                 type = checker.getTypeAtLocation(t);
             }
@@ -52,7 +104,6 @@ export function unwrapArrayItemType(type: ts.Type, typeChecker: ts.TypeChecker):
     return null;
 }
 
-
 export function isPrimitiveType(type: ts.Type | null) {
     if (!type) {
         return false;
@@ -85,7 +136,7 @@ export function isNumberType(type: ts.Type | null) {
     if (hasFlag(type, ts.TypeFlags.Number)) {
         return true;
     }
-    
+
     return false;
 }
 
@@ -116,4 +167,15 @@ export function hasFlag(type: ts.Type, flag: ts.TypeFlags): boolean {
 
 export function isMap(type: ts.Type | null): boolean {
     return !!(type && type.symbol?.name === 'Map');
-}
\ No newline at end of file
+}
+
+function markNodeSynthesized(node: ts.Node): ts.Node {
+    for(const c of node.getChildren()) {
+        markNodeSynthesized(c);
+    }
+    ts.setTextRange(node, {
+        pos: -1,
+        end: -1
+    });
+    return node;
+}
diff --git a/src.compiler/csharp/CSharpAst.ts b/src.compiler/csharp/CSharpAst.ts
index a738226a8..8a356f203 100644
--- a/src.compiler/csharp/CSharpAst.ts
+++ b/src.compiler/csharp/CSharpAst.ts
@@ -21,6 +21,7 @@ export enum SyntaxKind {
     PrimitiveTypeNode,
     EnumMember,
     ArrayTypeNode,
+    MapTypeNode,
 
     Block,
     EmptyStatement,
@@ -138,6 +139,7 @@ export interface NamedTypeDeclaration extends NamedElement, DocumentedElement, N
     typeParameters?: TypeParameterDeclaration[];
     visibility: Visibility;
     partial: boolean;
+    hasVirtualMembersOrSubClasses: boolean;
 }
 
 export interface ClassDeclaration extends NamedTypeDeclaration {
@@ -236,7 +238,7 @@ export interface UnresolvedTypeNode extends TypeNode {
     typeArguments?: UnresolvedTypeNode[];
 }
 
-export type TypeReferenceType = NamedTypeDeclaration | TypeParameterDeclaration | PrimitiveTypeNode | string;
+export type TypeReferenceType = NamedTypeDeclaration | TypeParameterDeclaration | TypeNode | string;
 export interface TypeReference extends TypeNode {
     reference: TypeReferenceType;
     typeArguments?: TypeNode[];
@@ -246,6 +248,13 @@ export interface ArrayTypeNode extends TypeNode {
     elementType: TypeNode;
 }
 
+export interface MapTypeNode extends TypeNode {
+    keyType: TypeNode;
+    keyIsValueType: boolean;
+    valueType: TypeNode;
+    valueIsValueType:boolean;
+}
+
 export interface FunctionTypeNode extends TypeNode {
     parameterTypes: TypeNode[];
     returnType: TypeNode;
@@ -492,6 +501,7 @@ export interface CatchClause extends Node {
 }
 
 // Node Tests
+export function isNode(node: any): node is Node { return typeof(node) === 'object' && 'nodeType' in node; }
 export function isSourceFile(node: Node): node is SourceFile { return node.nodeType === SyntaxKind.SourceFile; }
 export function isUsingDeclaration(node: Node): node is UsingDeclaration { return node.nodeType === SyntaxKind.UsingDeclaration; }
 export function isNamespaceDeclaration(node: Node): node is NamespaceDeclaration { return node.nodeType === SyntaxKind.NamespaceDeclaration; }
@@ -511,6 +521,7 @@ export function isFunctionTypeNode(node: Node): node is FunctionTypeNode { retur
 export function isPrimitiveTypeNode(node: Node): node is PrimitiveTypeNode { return node.nodeType === SyntaxKind.PrimitiveTypeNode; }
 export function isEnumMember(node: Node): node is EnumMember { return node.nodeType === SyntaxKind.EnumMember; }
 export function isArrayTypeNode(node: Node): node is ArrayTypeNode { return node.nodeType === SyntaxKind.ArrayTypeNode; }
+export function isMapTypeNode(node: Node): node is MapTypeNode { return node.nodeType === SyntaxKind.MapTypeNode; }
 
 export function isBlock(node: Node): node is Block { return node.nodeType === SyntaxKind.Block; }
 export function isEmptyStatement(node: Node): node is EmptyStatement { return node.nodeType === SyntaxKind.EmptyStatement; }
diff --git a/src.compiler/csharp/CSharpAstPrinter.ts b/src.compiler/csharp/CSharpAstPrinter.ts
index 37ec7f6c5..753ab8939 100644
--- a/src.compiler/csharp/CSharpAstPrinter.ts
+++ b/src.compiler/csharp/CSharpAstPrinter.ts
@@ -258,12 +258,10 @@ export default class CSharpAstPrinter extends AstPrinterBase {
         if (d.isAbstract) {
             this.write('abstract ');
         }
-
-        if (d.isVirtual) {
+        else if (d.isVirtual) {
             this.write('virtual ');
         }
-
-        if (d.isOverride) {
+        else if (d.isOverride) {
             this.write('override ');
         }
 
@@ -351,12 +349,10 @@ export default class CSharpAstPrinter extends AstPrinterBase {
             if (d.isAbstract) {
                 this.write('abstract ');
             }
-
-            if (d.isVirtual) {
+            else if (d.isVirtual) {
                 this.write('virtual ');
             }
-
-            if (d.isOverride) {
+            else if (d.isOverride) {
                 this.write('override ');
             }
         }
@@ -483,7 +479,7 @@ export default class CSharpAstPrinter extends AstPrinterBase {
                         this.write('System.Collections.IList');
                     } else {
                         if (forNew) {
-                            this.write('AlphaTab.Core.List<');
+                            this.write('AlphaTab.Collections.List<');
                         } else {
                             this.write('System.Collections.Generic.IList<');
                         }
@@ -492,6 +488,26 @@ export default class CSharpAstPrinter extends AstPrinterBase {
                     }
                 }
 
+                break;
+            case cs.SyntaxKind.MapTypeNode:
+                const mapType = type as cs.MapTypeNode;
+                if (!mapType.valueIsValueType) {
+                    if (forNew) {
+                        this.write('AlphaTab.Collections.Map<');
+                    } else {
+                        this.write('AlphaTab.Collections.IMap<');
+                    }
+                } else {
+                    if (forNew) {
+                        this.write('AlphaTab.Collections.ValueTypeMap<');
+                    } else {
+                        this.write('AlphaTab.Collections.IValueTypeMap<');
+                    }
+                }
+                this.writeType(mapType.keyType);
+                this.write(', ');
+                this.writeType(mapType.valueType);
+                this.write('>');
                 break;
             case cs.SyntaxKind.FunctionTypeNode:
                 const functionType = type as cs.FunctionTypeNode;
@@ -699,7 +715,7 @@ export default class CSharpAstPrinter extends AstPrinterBase {
 
     protected writeNonNullExpression(expr: cs.NonNullExpression) {
         this.writeExpression(expr.expression);
-        if(!cs.isNonNullExpression(expr)) {
+        if (!cs.isNonNullExpression(expr)) {
             this.write('!');
         }
     }
diff --git a/src.compiler/csharp/CSharpAstTransformer.ts b/src.compiler/csharp/CSharpAstTransformer.ts
index a1c06e800..e96405a9d 100644
--- a/src.compiler/csharp/CSharpAstTransformer.ts
+++ b/src.compiler/csharp/CSharpAstTransformer.ts
@@ -296,10 +296,6 @@ export default class CSharpAstTransformer {
         additionalNestedNonExportsDeclarations?: ts.Declaration[],
         globalStatements?: ts.Statement[]
     ): ts.Node {
-        if (this.shouldSkip(node, false)) {
-            return node;
-        }
-
         if (ts.isClassDeclaration(node)) {
             this.visitClassDeclaration(
                 node,
@@ -330,12 +326,17 @@ export default class CSharpAstTransformer {
         }
         const tags = ts.getJSDocTags(node).filter(t => t.tagName.text === 'target');
         if (tags.length > 0) {
-            return !tags.find(t => t.comment === 'csharp');
+            return !tags.find(t => t.comment === this.targetTag);
         }
 
         return false;
     }
 
+    public get targetTag(): string {
+        return 'csharp';
+    }
+
+
     protected visitEnumDeclaration(node: ts.EnumDeclaration) {
         const csEnum: cs.EnumDeclaration = {
             visibility: cs.Visibility.Public,
@@ -346,7 +347,8 @@ export default class CSharpAstTransformer {
             tsNode: node,
             partial: false,
             skipEmit: this.shouldSkip(node, false),
-            tsSymbol: this._context.getSymbolForDeclaration(node)
+            tsSymbol: this._context.getSymbolForDeclaration(node),
+            hasVirtualMembersOrSubClasses: false
         };
 
         if (node.name) {
@@ -398,7 +400,8 @@ export default class CSharpAstTransformer {
             tsNode: node,
             skipEmit: this.shouldSkip(node, false),
             partial: !!ts.getJSDocTags(node).find(t => t.tagName.text === 'partial'),
-            tsSymbol: this._context.getSymbolForDeclaration(node)
+            tsSymbol: this._context.getSymbolForDeclaration(node),
+            hasVirtualMembersOrSubClasses: false
         };
 
         if (node.name) {
@@ -424,7 +427,9 @@ export default class CSharpAstTransformer {
             });
         }
 
-        node.members.forEach(m => this.visitInterfaceElement(csInterface, m));
+        if(!csInterface.skipEmit){
+            node.members.forEach(m => this.visitInterfaceElement(csInterface, m));
+        }
 
         this._csharpFile.namespace.declarations.push(csInterface);
         this._context.registerSymbol(csInterface);
@@ -499,7 +504,8 @@ export default class CSharpAstTransformer {
             parent: this._csharpFile.namespace,
             isAbstract: false,
             partial: false,
-            members: []
+            members: [],
+            hasVirtualMembersOrSubClasses: false
         };
 
         if (this._testClassAttribute.length > 0) {
@@ -738,7 +744,8 @@ export default class CSharpAstTransformer {
             partial: !!ts.getJSDocTags(node).find(t => t.tagName.text === 'partial'),
             members: [],
             skipEmit: this.shouldSkip(node, false),
-            tsSymbol: this._context.getSymbolForDeclaration(node)
+            tsSymbol: this._context.getSymbolForDeclaration(node),
+            hasVirtualMembersOrSubClasses: false
         };
 
         if (node.name) {
@@ -773,32 +780,34 @@ export default class CSharpAstTransformer {
             });
         }
 
-        node.members.forEach(m => this.visitClassElement(csClass, m));
+        if (!csClass.skipEmit) {
+            node.members.forEach(m => this.visitClassElement(csClass, m));
 
-        if (globalStatements && globalStatements.length > 0) {
-            const staticConstructor = {
-                parent: csClass,
-                isStatic: true,
-                name: 'cctor',
-                nodeType: cs.SyntaxKind.ConstructorDeclaration,
-                parameters: [],
-                visibility: cs.Visibility.None,
-                tsNode: node,
-                body: {
-                    parent: null,
-                    nodeType: cs.SyntaxKind.Block,
-                    statements: []
-                } as cs.Block
-            } as cs.ConstructorDeclaration;
+            if (globalStatements && globalStatements.length > 0) {
+                const staticConstructor = {
+                    parent: csClass,
+                    isStatic: true,
+                    name: 'cctor',
+                    nodeType: cs.SyntaxKind.ConstructorDeclaration,
+                    parameters: [],
+                    visibility: cs.Visibility.None,
+                    tsNode: node,
+                    body: {
+                        parent: null,
+                        nodeType: cs.SyntaxKind.Block,
+                        statements: []
+                    } as cs.Block
+                } as cs.ConstructorDeclaration;
 
-            globalStatements.forEach(s => {
-                const st = this.visitStatement(staticConstructor.body!, s)!;
-                if (st) {
-                    (staticConstructor.body as cs.Block).statements.push(st);
-                }
-            });
+                globalStatements.forEach(s => {
+                    const st = this.visitStatement(staticConstructor.body!, s)!;
+                    if (st) {
+                        (staticConstructor.body as cs.Block).statements.push(st);
+                    }
+                });
 
-            csClass.members.push(staticConstructor);
+                csClass.members.push(staticConstructor);
+            }
         }
 
         this._csharpFile.namespace.declarations.push(csClass);
@@ -970,6 +979,10 @@ export default class CSharpAstTransformer {
                 tsNode: classElement,
                 body: classElement.body ? this.visitBlock(member, classElement.body) : null
             } as cs.PropertyAccessorDeclaration;
+
+            if (this._context.markOverride(classElement)) {
+                member.isOverride = true;
+            }
         } else {
             const signature = this._context.typeChecker.getSignatureFromDeclaration(classElement);
             const returnType = this._context.typeChecker.getReturnTypeOfSignature(signature!);
@@ -984,17 +997,13 @@ export default class CSharpAstTransformer {
                 parent: parent,
                 visibility: this.mapVisibility(classElement),
                 type: this.createUnresolvedTypeNode(null, classElement.type ?? classElement, returnType),
-                skipEmit: this.shouldSkip(classElement, false)
+                skipEmit: this.shouldSkip(classElement, false),
+                tsNode: classElement,
+                tsSymbol: this._context.getSymbolForDeclaration(classElement),
             };
 
-            if (newProperty.visibility === cs.Visibility.Public || newProperty.visibility === cs.Visibility.Protected) {
-                if (this._context.isOverride(classElement)) {
-                    newProperty.isVirtual = false;
-                    newProperty.isOverride = true;
-                } else {
-                    newProperty.isVirtual = true;
-                    newProperty.isOverride = false;
-                }
+            if (this._context.markOverride(classElement)) {
+                newProperty.isOverride = true;
             }
 
             if (classElement.modifiers) {
@@ -1040,6 +1049,11 @@ export default class CSharpAstTransformer {
                 tsNode: classElement,
                 body: classElement.body ? this.visitBlock(member, classElement.body) : null
             } as cs.PropertyAccessorDeclaration;
+
+            if (this._context.markOverride(classElement)) {
+                member.isOverride = true;
+            }
+
             return member.setAccessor;
         } else {
             const signature = this._context.typeChecker.getSignatureFromDeclaration(classElement);
@@ -1055,17 +1069,13 @@ export default class CSharpAstTransformer {
                 parent: parent,
                 visibility: this.mapVisibility(classElement),
                 type: this.createUnresolvedTypeNode(null, classElement.type ?? classElement, returnType),
-                skipEmit: this.shouldSkip(classElement, false)
+                skipEmit: this.shouldSkip(classElement, false),
+                tsNode: classElement,
+                tsSymbol: this._context.getSymbolForDeclaration(classElement),
             };
 
-            if (newProperty.visibility === cs.Visibility.Public || newProperty.visibility === cs.Visibility.Protected) {
-                if (this._context.isOverride(classElement)) {
-                    newProperty.isVirtual = false;
-                    newProperty.isOverride = true;
-                } else {
-                    newProperty.isVirtual = true;
-                    newProperty.isOverride = false;
-                }
+            if (this._context.markOverride(classElement)) {
+                newProperty.isOverride = true;
             }
 
             if (classElement.modifiers) {
@@ -1119,23 +1129,18 @@ export default class CSharpAstTransformer {
             type: this.createUnresolvedTypeNode(null, classElement.type ?? classElement, type),
             visibility: visibility,
             tsNode: classElement,
+            tsSymbol: this._context.getSymbolForDeclaration(classElement),
             skipEmit: this.shouldSkip(classElement, false)
         };
 
-        if (csProperty.visibility === cs.Visibility.Public || csProperty.visibility === cs.Visibility.Protected) {
-            if (this._context.isOverride(classElement)) {
-                csProperty.isVirtual = false;
-                csProperty.isOverride = true;
-            } else {
-                csProperty.isVirtual = true;
-                csProperty.isOverride = false;
-            }
-        }
-
         if (classElement.name) {
             csProperty.documentation = this.visitDocumentation(classElement.name);
         }
 
+        if (this._context.markOverride(classElement)) {
+            csProperty.isOverride = true;
+        }
+
         let isReadonly = false;
         if (classElement.modifiers) {
             classElement.modifiers.forEach(m => {
@@ -1220,6 +1225,7 @@ export default class CSharpAstTransformer {
             returnType: this.createUnresolvedTypeNode(null, classElement.type ?? classElement, returnType),
             visibility: this.mapVisibility(classElement),
             tsNode: classElement,
+            tsSymbol: this._context.getSymbolForDeclaration(classElement),
             skipEmit: this.shouldSkip(classElement, false)
         };
 
@@ -1227,14 +1233,8 @@ export default class CSharpAstTransformer {
             csMethod.documentation = this.visitDocumentation(classElement.name);
         }
 
-        if (csMethod.visibility === cs.Visibility.Public || csMethod.visibility === cs.Visibility.Protected) {
-            if (this._context.isOverride(classElement)) {
-                csMethod.isVirtual = false;
-                csMethod.isOverride = true;
-            } else {
-                csMethod.isVirtual = true;
-                csMethod.isOverride = false;
-            }
+        if (this._context.markOverride(classElement)) {
+            csMethod.isOverride = true;
         }
 
         if (classElement.modifiers) {
@@ -2822,28 +2822,7 @@ export default class CSharpAstTransformer {
 
     protected visitArrayLiteralExpression(parent: cs.Node, expression: ts.ArrayLiteralExpression) {
         if (this.isMapInitializer(expression)) {
-            const csExpr = {
-                parent: parent,
-                tsNode: expression,
-                nodeType: cs.SyntaxKind.InvocationExpression,
-                arguments: [],
-                expression: {} as cs.Expression
-            } as cs.InvocationExpression;
-
-            csExpr.expression = this.makeMemberAccess(
-                csExpr,
-                this._context.makeTypeName('alphaTab.core.TypeHelper'),
-                this._context.toPascalCase('createMapEntry')
-            );
-
-            expression.elements.forEach(e => {
-                const ex = this.visitExpression(csExpr, e);
-                if (ex) {
-                    csExpr.arguments.push(ex);
-                }
-            });
-
-            return csExpr;
+            return this.createMapEntry(parent, expression);
         } else if (this.isSetInitializer(expression)) {
             const csExpr = {
                 parent: parent,
@@ -2890,6 +2869,31 @@ export default class CSharpAstTransformer {
             return csExpr;
         }
     }
+    protected createMapEntry(parent: cs.Node, expression: ts.ArrayLiteralExpression): cs.Expression {
+        const csExpr = {
+            parent: parent,
+            tsNode: expression,
+            nodeType: cs.SyntaxKind.InvocationExpression,
+            arguments: [],
+            expression: {} as cs.Expression
+        } as cs.InvocationExpression;
+
+        csExpr.expression = this.makeMemberAccess(
+            csExpr,
+            this._context.makeTypeName('alphaTab.core.TypeHelper'),
+            this._context.toPascalCase('createMapEntry')
+        );
+
+        expression.elements.forEach(e => {
+            const ex = this.visitExpression(csExpr, e);
+            if (ex) {
+                csExpr.arguments.push(ex);
+            }
+        });
+
+        return csExpr;
+    }
+
     protected isMapInitializer(expression: ts.ArrayLiteralExpression) {
         const isCandidate =
             expression.elements.length === 2 &&
@@ -2976,6 +2980,12 @@ export default class CSharpAstTransformer {
                         return 'TrimStart';
                 }
                 break;
+            case 'Number':
+                switch (symbol.name) {
+                    case 'toString':
+                        return 'ToInvariantString';
+                }
+                break;
         }
         return null;
     }
@@ -3083,7 +3093,7 @@ export default class CSharpAstTransformer {
         } as cs.InvocationExpression;
         const memberAccess = (callExpr.expression = {
             expression: null!,
-            member: this._context.toPascalCase('toString'),
+            member: this._context.toPascalCase('toInvariantString'),
             parent: callExpr,
             tsNode: expr.tsNode,
             nodeType: cs.SyntaxKind.MemberAccessExpression
@@ -3099,22 +3109,6 @@ export default class CSharpAstTransformer {
         expr.parent = par;
         memberAccess.expression = par;
 
-        const invariantCultureInfo = {
-            parent: callExpr,
-            nodeType: cs.SyntaxKind.MemberAccessExpression,
-            tsNode: expr.tsNode,
-            expression: null!,
-            member: this._context.toPascalCase('invariantCulture')
-        } as cs.MemberAccessExpression;
-
-        invariantCultureInfo.expression = {
-            parent: invariantCultureInfo,
-            tsNode: expr.tsNode,
-            nodeType: cs.SyntaxKind.Identifier,
-            text: this._context.makeTypeName('system.globalization.CultureInfo')
-        } as cs.Identifier;
-        callExpr.arguments.push(invariantCultureInfo);
-
         return callExpr;
     }
 
@@ -3217,6 +3211,13 @@ export default class CSharpAstTransformer {
             nodeType: cs.SyntaxKind.InvocationExpression
         } as cs.InvocationExpression;
 
+        // number.ToString
+        const isNumberToString =
+            ts.isPropertyAccessExpression(expression.expression) &&
+            this._context.typeChecker.getTypeAtLocation(expression.expression.expression).flags & ts.TypeFlags.Number &&
+            (expression.expression.name as ts.Identifier).text === 'toString' &&
+            expression.arguments.length === 0;
+
         callExpression.expression = this.visitExpression(callExpression, expression.expression)!;
         if (!callExpression.expression) {
             return null;
@@ -3236,31 +3237,6 @@ export default class CSharpAstTransformer {
             }
         });
 
-        // number.ToString
-        const isNumberToString =
-            ts.isPropertyAccessExpression(expression.expression) &&
-            this._context.typeChecker.getTypeAtLocation(expression.expression.expression).flags & ts.TypeFlags.Number &&
-            (expression.expression.name as ts.Identifier).text === 'toString' &&
-            expression.arguments.length === 0;
-
-        if (isNumberToString) {
-            const invariantCultureInfo = {
-                parent: parent,
-                nodeType: cs.SyntaxKind.MemberAccessExpression,
-                tsNode: expression,
-                expression: null!,
-                member: this._context.toPascalCase('invariantCulture')
-            } as cs.MemberAccessExpression;
-
-            invariantCultureInfo.expression = {
-                parent: invariantCultureInfo,
-                tsNode: expression.expression,
-                nodeType: cs.SyntaxKind.Identifier,
-                text: this._context.makeTypeName('system.globalization.CultureInfo')
-            } as cs.Identifier;
-
-            callExpression.arguments.push(invariantCultureInfo);
-        }
 
         if (expression.typeArguments) {
             callExpression.typeArguments = [];
@@ -3448,7 +3424,7 @@ export default class CSharpAstTransformer {
                 (node.tsSymbol.flags & ts.SymbolFlags.Variable) === ts.SymbolFlags.Variable ||
                 (node.tsSymbol.flags & ts.SymbolFlags.EnumMember) === ts.SymbolFlags.EnumMember ||
                 (node.tsSymbol.flags & ts.SymbolFlags.FunctionScopedVariable) ===
-                    ts.SymbolFlags.FunctionScopedVariable ||
+                ts.SymbolFlags.FunctionScopedVariable ||
                 (node.tsSymbol.flags & ts.SymbolFlags.BlockScopedVariable) === ts.SymbolFlags.BlockScopedVariable
             ) {
                 let smartCastType = this._context.getSmartCastType(expression);
diff --git a/src.compiler/csharp/CSharpEmitterContext.ts b/src.compiler/csharp/CSharpEmitterContext.ts
index 05984d06a..81260748a 100644
--- a/src.compiler/csharp/CSharpEmitterContext.ts
+++ b/src.compiler/csharp/CSharpEmitterContext.ts
@@ -8,6 +8,7 @@ export default class CSharpEmitterContext {
     private _fileLookup: Map<ts.SourceFile, cs.SourceFile> = new Map();
     private _symbolLookup: Map<SymbolKey, cs.NamedElement & cs.Node> = new Map();
     private _exportedSymbols: Map<SymbolKey, boolean> = new Map();
+    private _virtualSymbols: Map<SymbolKey, boolean> = new Map();
     private _symbolConst: Map<SymbolKey, boolean> = new Map();
 
     private _diagnostics: ts.Diagnostic[] = [];
@@ -314,38 +315,7 @@ export default class CSharpEmitterContext {
                     mapValueType = this.getTypeFromTsType(node, mapType.typeArguments[1]);
                 }
 
-                let isValueType = false;
-                if (mapValueType) {
-                    switch (mapValueType.nodeType) {
-                        case cs.SyntaxKind.PrimitiveTypeNode:
-                            switch ((mapValueType as cs.PrimitiveTypeNode).type) {
-                                case cs.PrimitiveType.Bool:
-                                case cs.PrimitiveType.Int:
-                                case cs.PrimitiveType.Double:
-                                    isValueType = true;
-                                    break;
-                            }
-                            break;
-                        case cs.SyntaxKind.TypeReference:
-                            const ref = (mapValueType as cs.TypeReference).reference;
-                            if (typeof ref !== 'string') {
-                                switch (ref.nodeType) {
-                                    case cs.SyntaxKind.EnumDeclaration:
-                                        isValueType = true;
-                                        break;
-                                }
-                            }
-                            break;
-                    }
-                }
-
-                return {
-                    nodeType: cs.SyntaxKind.TypeReference,
-                    parent: node.parent,
-                    tsNode: node.tsNode,
-                    reference: this.buildCoreNamespace(tsSymbol) + (isValueType ? 'ValueTypeMap' : 'Map'),
-                    typeArguments: [mapKeyType, mapValueType]
-                } as cs.TypeReference;
+                return this.createMapType(tsSymbol, node, mapKeyType!, mapValueType!);
             case 'Array':
                 const arrayType = tsType as ts.TypeReference;
                 let arrayElementType: cs.TypeNode | null = null;
@@ -364,13 +334,7 @@ export default class CSharpEmitterContext {
                     } as cs.PrimitiveTypeNode;
                 }
 
-                return {
-                    nodeType: cs.SyntaxKind.ArrayTypeNode,
-                    parent: node.parent,
-                    tsNode: node.tsNode,
-                    elementType: arrayElementType
-                } as cs.ArrayTypeNode;
-
+                return this.createArrayListType(tsSymbol, node, arrayElementType);
             case ts.InternalSymbolName.Type:
                 let csType: cs.TypeNode | null = null;
 
@@ -389,6 +353,57 @@ export default class CSharpEmitterContext {
         }
     }
 
+    protected createArrayListType(tsSymbol: ts.Symbol, node: cs.Node, arrayElementType: cs.TypeNode): cs.TypeNode {
+        return {
+            nodeType: cs.SyntaxKind.ArrayTypeNode,
+            parent: node.parent,
+            tsNode: node.tsNode,
+            elementType: arrayElementType
+        } as cs.ArrayTypeNode;
+    }
+
+    protected createMapType(
+        symbol: ts.Symbol,
+        node: cs.Node,
+        mapKeyType: cs.TypeNode,
+        mapValueType: cs.TypeNode
+    ): cs.TypeNode {
+        return {
+            nodeType: cs.SyntaxKind.MapTypeNode,
+            parent: node.parent,
+            tsNode: node.tsNode,
+            keyType: mapKeyType,
+            valueType: mapValueType,
+            valueIsValueType: this.isCsValueType(mapValueType),
+            keyIsValueType: this.isCsValueType(mapKeyType)
+        } as cs.MapTypeNode;
+    }
+
+    protected isCsValueType(mapValueType: cs.TypeNode) {
+        if (mapValueType) {
+            switch (mapValueType.nodeType) {
+                case cs.SyntaxKind.PrimitiveTypeNode:
+                    switch ((mapValueType as cs.PrimitiveTypeNode).type) {
+                        case cs.PrimitiveType.Bool:
+                        case cs.PrimitiveType.Int:
+                        case cs.PrimitiveType.Double:
+                            return true;
+                    }
+                    break;
+                case cs.SyntaxKind.TypeReference:
+                    const ref = (mapValueType as cs.TypeReference).reference;
+                    if (typeof ref !== 'string') {
+                        switch (ref.nodeType) {
+                            case cs.SyntaxKind.EnumDeclaration:
+                                return true;
+                        }
+                    }
+                    break;
+            }
+        }
+        return false;
+    }
+
     private resolveFunctionTypeFromTsType(node: cs.Node, tsType: ts.Type): cs.TypeNode | null {
         // typescript compiler API somehow does not provide proper type symbols
         // for function types, we need to attempt resolving the types via the function type declaration
@@ -754,8 +769,13 @@ export default class CSharpEmitterContext {
         return result;
     }
 
-    private buildCoreNamespace(aliasSymbol: ts.Symbol) {
+    protected buildCoreNamespace(aliasSymbol: ts.Symbol) {
         let suffix = '';
+        
+        if(aliasSymbol.name === 'Map') {
+            return this.toPascalCase('alphaTab.collections') + suffix + '.';           
+        }
+
         if (aliasSymbol.declarations) {
             for (const decl of aliasSymbol.declarations) {
                 let fileName = path.basename(decl.getSourceFile().fileName).toLowerCase();
@@ -775,9 +795,13 @@ export default class CSharpEmitterContext {
                 }
             }
         }
+
         return this.toPascalCase('alphaTab.core') + suffix + '.';
     }
     protected toCoreTypeName(s: string) {
+        if (s === 'Map') {
+            return 'IMap'
+        }
         return s;
     }
 
@@ -844,8 +868,8 @@ export default class CSharpEmitterContext {
         const declaration = symbol.valueDeclaration
             ? symbol.valueDeclaration
             : symbol.declarations && symbol.declarations.length > 0
-            ? symbol.declarations[0]
-            : undefined;
+                ? symbol.declarations[0]
+                : undefined;
 
         if (declaration) {
             return symbol.name + '_' + declaration.getSourceFile().fileName + '_' + declaration.pos;
@@ -1052,7 +1076,7 @@ export default class CSharpEmitterContext {
             return null;
         }
         const declarations = symbol.declarations;
-        if(!declarations || declarations.length === 0){
+        if (!declarations || declarations.length === 0) {
             return null;
         }
 
@@ -1166,7 +1190,12 @@ export default class CSharpEmitterContext {
         return false;
     }
 
-    public isOverride(classElement: ts.ClassElement): boolean {
+    public markAsSubclassed(classElement: ts.Symbol) {
+        const key = this.getSymbolKey(classElement);
+        this._virtualSymbols.set(key, true);
+    }
+    
+    public markOverride(classElement: ts.ClassElement): boolean {
         let parent: ts.Node = classElement;
         while (parent.kind !== ts.SyntaxKind.ClassDeclaration) {
             if (parent.parent) {
@@ -1187,45 +1216,42 @@ export default class CSharpEmitterContext {
             return false;
         }
 
-        if (this.isClassElementOverride(classType, classElement)) {
-            return true;
+
+        const overridden = this.getOverriddenMembers(classType, classElement);
+        if(overridden.length > 0) {
+            const member = this.typeChecker.getSymbolAtLocation(classElement) ?? this.typeChecker.getSymbolAtLocation(classElement.name!);
+            this._virtualSymbols.set(this.getSymbolKey(member), true);
+
+            for (const s of overridden) {
+                const symbolKey = this.getSymbolKey(s);
+                this._virtualSymbols.set(symbolKey, true);
+            }
         }
+     
 
-        return false;
+        return overridden.length > 0;
     }
 
-    protected isClassElementOverride(classType: ts.InterfaceType, classElement: ts.ClassElement) {
-        return this.hasAnyBaseTypeClassMember(classType, classElement.name!.getText());
+    protected getOverriddenMembers(classType: ts.InterfaceType, classElement: ts.ClassElement): ts.Symbol[] {
+        const symbols: ts.Symbol[] = [];
+        this.collectOverriddenMembersByName(symbols, classType, classElement.name!.getText(), false, false);
+        return symbols;
     }
 
-    protected hasAnyBaseTypeClassMember(classType: ts.Type, memberName: string, allowInterfaces: boolean = false) {
-        const baseTypes = classType.getBaseTypes();
-        if (!baseTypes) {
-            return false;
+    protected collectOverriddenMembersByName(symbols: ts.Symbol[], classType: ts.InterfaceType, memberName: string, includeOwnMembers: boolean = false, allowInterfaces: boolean = false) {
+        const member = classType.symbol?.members?.get(ts.escapeLeadingUnderscores(memberName));
+        if (includeOwnMembers && member) {
+            symbols.push(member);
         }
 
-        for (const baseType of baseTypes) {
-            if (
-                ((allowInterfaces && baseType.isClassOrInterface()) || baseType.isClass()) &&
-                this.hasClassMember(baseType, memberName)
-            ) {
-                return true;
+        const baseTypes = classType.getBaseTypes();
+        if (baseTypes) {
+            for (const baseType of baseTypes) {
+                if (((allowInterfaces && baseType.isClassOrInterface()) || baseType.isClass())) {
+                    this.collectOverriddenMembersByName(symbols, baseType, memberName, true, allowInterfaces);
+                }
             }
         }
-
-        return false;
-    }
-
-    protected hasClassMember(baseType: ts.Type, name: string): boolean {
-        if (
-            baseType.symbol &&
-            baseType.symbol.members &&
-            baseType.symbol.members.has(ts.escapeLeadingUnderscores(name))
-        ) {
-            return true;
-        }
-
-        return this.hasAnyBaseTypeClassMember(baseType, name);
     }
 
     public isValueTypeExpression(expression: ts.NonNullExpression) {
@@ -1266,31 +1292,100 @@ export default class CSharpEmitterContext {
     }
 
     public rewriteVisibilities() {
-        const visited: Set<SymbolKey> = new Set();
+        const visitedVisibility: Set<SymbolKey> = new Set();
+        const visitedVirtual: Map<SymbolKey, boolean> = new Map();
         for (const kvp of this._symbolLookup) {
             const symbolKey = this.getSymbolKey(kvp[1].tsSymbol!);
             switch (kvp[1].nodeType) {
                 case cs.SyntaxKind.ClassDeclaration:
                 case cs.SyntaxKind.EnumDeclaration:
                 case cs.SyntaxKind.InterfaceDeclaration:
-                    if (!visited.has(symbolKey)) {
-                        const csType = kvp[1] as cs.NamedTypeDeclaration;
+                    const csType = kvp[1] as cs.NamedTypeDeclaration;
+                    if (!visitedVisibility.has(symbolKey)) {
                         const shouldBePublic = !!ts
                             .getJSDocTags(csType.tsNode!)
                             .find(t => t.tagName.text === 'csharp_public');
                         if (csType.visibility === cs.Visibility.Public || shouldBePublic) {
                             if (this._exportedSymbols.has(symbolKey) || shouldBePublic) {
-                                this.makePublic(csType, visited);
+                                this.makePublic(csType, visitedVisibility);
                             } else {
                                 csType.visibility = cs.Visibility.Internal;
                             }
                         }
                     }
+
+                    if (this.makeVirtual(csType, visitedVirtual)) {
+                        csType.hasVirtualMembersOrSubClasses = true;
+                    }
+
                     break;
             }
         }
     }
 
+    private makeVirtual(node: cs.Node, visited: Map<SymbolKey, boolean>): boolean {
+        const x = this.getSymbolKey(this.getSymbolForDeclaration(node.tsNode!));
+        if (visited.has(x)) {
+            return visited.get(x)!;
+        }
+
+        let hasVirtualMember = false;
+
+        switch (node.nodeType) {
+            case cs.SyntaxKind.ClassDeclaration:
+                const csClass = node as cs.ClassDeclaration;
+                csClass.members.forEach(m => {
+                    if(this.makeVirtual(m, visited)) {
+                        hasVirtualMember = true;
+                    }
+                });
+
+                let baseClass = csClass.baseClass;
+                while(baseClass != null) {
+                    if(cs.isTypeReference(baseClass)) {
+                        const ref = baseClass.reference;
+                        if(cs.isNode(ref) && cs.isClassDeclaration(ref)) {
+                            ref.hasVirtualMembersOrSubClasses = true;
+                            baseClass = ref;
+                        } else {
+                            break;
+                        }
+                    } else {
+                        break;
+                    }
+                }
+
+                break;
+
+            case cs.SyntaxKind.MethodDeclaration:
+                const csMethod = node as cs.MethodDeclaration;
+                
+                const methodKey = this.getSymbolKey(csMethod.tsSymbol!);
+                if(!csMethod.isOverride && this._virtualSymbols.has(methodKey)) {
+                    csMethod.isVirtual = true;
+                    hasVirtualMember = true;
+                }
+
+                break;
+
+            case cs.SyntaxKind.PropertyDeclaration:
+                const csProperty = node as cs.PropertyDeclaration;
+
+                const propKey = this.getSymbolKey(csProperty.tsSymbol!);
+                if(!csProperty.isOverride && this._virtualSymbols.has(propKey)) {
+                    csProperty.isVirtual = true;
+                    hasVirtualMember = true;
+                }
+
+                break;
+        }
+
+        visited.set(x, hasVirtualMember);
+
+        return hasVirtualMember;
+    }
+
+
     private makePublic(node: cs.Node, visited: Set<SymbolKey>) {
         if (node.tsSymbol) {
             const x = this.getSymbolKey(node.tsSymbol);
@@ -1372,6 +1467,11 @@ export default class CSharpEmitterContext {
                     this.makePublic(csArrayType.elementType, visited);
                 }
                 break;
+            case cs.SyntaxKind.MapTypeNode:
+                const mapType = node as cs.MapTypeNode;
+                this.makePublic(mapType.keyType, visited);
+                this.makePublic(mapType.valueType, visited);
+                break;
         }
     }
 
diff --git a/src.compiler/kotlin/KotlinAstPrinter.ts b/src.compiler/kotlin/KotlinAstPrinter.ts
index bacecde56..c20c3186b 100644
--- a/src.compiler/kotlin/KotlinAstPrinter.ts
+++ b/src.compiler/kotlin/KotlinAstPrinter.ts
@@ -246,7 +246,7 @@ export default class KotlinAstPrinter extends AstPrinterBase {
 
         if (d.isAbstract) {
             this.write('abstract ');
-        } else {
+        } else if(d.hasVirtualMembersOrSubClasses) {
             this.write('open ');
         }
 
@@ -343,12 +343,12 @@ export default class KotlinAstPrinter extends AstPrinterBase {
                 defaultConstructor.parameters = constructorDeclaration.parameters;
                 defaultConstructor.baseConstructorArguments = constructorDeclaration.parameters.map(
                     p =>
-                        ({
-                            parent: defaultConstructor,
-                            nodeType: cs.SyntaxKind.Identifier,
-                            text: p.name,
-                            tsNode: defaultConstructor.tsNode
-                        } as cs.Identifier)
+                    ({
+                        parent: defaultConstructor,
+                        nodeType: cs.SyntaxKind.Identifier,
+                        text: p.name,
+                        tsNode: defaultConstructor.tsNode
+                    } as cs.Identifier)
                 );
                 this.writeMember(defaultConstructor);
             } else {
@@ -617,22 +617,60 @@ export default class KotlinAstPrinter extends AstPrinterBase {
                     this.writeType(arrayType.elementType);
                     this.write('>');
                 } else {
-                    const isDynamicArray =
-                        cs.isPrimitiveTypeNode(arrayType.elementType) &&
-                        arrayType.elementType.type === cs.PrimitiveType.Dynamic;
-                    if (isDynamicArray && !forNew) {
-                        this.write('kotlin.collections.MutableList<*>');
+                    var elementTypeName = this.getContainerTypeName(arrayType.elementType);
+                    if (elementTypeName) {
+                        this.write('alphaTab.collections.');
+                        this.write(elementTypeName);
+                        this.write('List');
                     } else {
-                        if (forNew) {
-                            this.write('alphaTab.core.LateInitList<');
+                        const isDynamicArray =
+                            cs.isPrimitiveTypeNode(arrayType.elementType) &&
+                            arrayType.elementType.type === cs.PrimitiveType.Dynamic;
+                        if (isDynamicArray && !forNew) {
+                            this.write('alphaTab.collections.List<*>');
                         } else {
-                            this.write('kotlin.collections.MutableList<');
+                            if (forNew) {
+                                this.write('alphaTab.collections.List<');
+                            } else {
+                                this.write('alphaTab.collections.List<');
+                            }
+                            this.writeType(arrayType.elementType);
+                            this.write('>');
                         }
-                        this.writeType(arrayType.elementType);
-                        this.write('>');
                     }
                 }
 
+                break;
+            case cs.SyntaxKind.MapTypeNode:
+                const mapType = type as cs.MapTypeNode;
+
+                var keyTypeName = this.getContainerTypeName(mapType.keyType);
+                var valueTypeName = this.getContainerTypeName(mapType.valueType);
+
+                this.write('alphaTab.collections.');
+                if (keyTypeName && valueTypeName) {
+
+                    this.write(keyTypeName);
+                    this.write(valueTypeName);
+                    this.write('Map');
+                } else if (keyTypeName) {
+                    this.write(keyTypeName);
+                    this.write('ObjectMap<');
+                    this.writeType(mapType.valueType);
+                    this.write('>');
+                } else if (valueTypeName) {
+                    this.write('Object');
+                    this.write(valueTypeName);
+                    this.write('Map<');
+                    this.writeType(mapType.keyType);
+                    this.write('>');
+                } else {
+                    this.write('Map<');
+                    this.writeType(mapType.keyType);
+                    this.write(', ');
+                    this.writeType(mapType.valueType);
+                    this.write('>');
+                }
                 break;
             case cs.SyntaxKind.FunctionTypeNode:
                 const functionType = type as cs.FunctionTypeNode;
@@ -682,6 +720,25 @@ export default class KotlinAstPrinter extends AstPrinterBase {
         }
     }
 
+    private getContainerTypeName(type: cs.TypeNode): string | null {
+        switch (type.nodeType) {
+            case cs.SyntaxKind.PrimitiveTypeNode:
+                if (type.isNullable) {
+                    return null;
+                }
+                switch ((type as cs.PrimitiveTypeNode).type) {
+                    case cs.PrimitiveType.Bool:
+                        return 'Boolean';
+                    case cs.PrimitiveType.Int:
+                        return 'Int';
+                    case cs.PrimitiveType.Double:
+                        return 'Double';
+                }
+                break;
+        }
+        return null;
+    }
+
     protected writeTypeOfExpression(expr: cs.TypeOfExpression) {
         if (expr.expression) {
             this.writeExpression(expr.expression);
@@ -875,11 +932,26 @@ export default class KotlinAstPrinter extends AstPrinterBase {
     protected writeArrayCreationExpression(expr: cs.ArrayCreationExpression) {
         if (expr.type) {
             if (expr.values) {
-                this.write('arrayListOf');
-                if (expr.type && cs.isArrayTypeNode(expr.type)) {
-                    this.write('<');
-                    this.writeType(expr.type.elementType);
-                    this.write('>');
+                let elementType: cs.TypeNode | null = null;
+                if (cs.isArrayTypeNode(expr.type)) {
+                    elementType = expr.type.elementType;
+                }
+                else if (cs.isTypeReference(expr.type) && typeof (expr.type.reference) !== 'string' && cs.isArrayTypeNode(expr.type.reference)) {
+                    elementType = expr.type.reference.elementType;
+                }
+
+                let type = elementType ? this.getContainerTypeName(elementType) : null;
+                this.write('alphaTab.collections.')
+                if (type) {
+                    this.write(type);
+                    this.write('List')
+                } else {
+                    this.write('List');
+                    if (expr.type && cs.isArrayTypeNode(expr.type)) {
+                        this.write('<');
+                        this.writeType(expr.type.elementType);
+                        this.write('>');
+                    }
                 }
                 this.writeLine('(');
                 this._indent++;
@@ -898,7 +970,9 @@ export default class KotlinAstPrinter extends AstPrinterBase {
                 this.write(')');
             }
         } else if (expr.values && expr.values.length > 0) {
-            this.write('arrayListOf(');
+            // TODO: check for typed array creation
+            this.write('alphaTab.collections.')
+            this.write('List(');
             this.writeCommaSeparated(expr.values, v => {
                 if (expr.values!.length > 10) {
                     this.writeLine();
@@ -1147,7 +1221,7 @@ export default class KotlinAstPrinter extends AstPrinterBase {
     }
 
     protected writeForStatement(s: cs.ForStatement) {
-        if (!this.tryWriteForRange(s)) {
+        if (/*!this.tryWriteForRange(s)*/ true) {
             this.write('if(true) ');
             this.beginBlock();
 
@@ -1423,7 +1497,7 @@ export default class KotlinAstPrinter extends AstPrinterBase {
         this.writeLine(`import ${using.namespaceOrTypeName}.*`);
     }
 
-    protected writeSemicolon() {
+    protected override writeSemicolon() {
         this.writeLine();
     }
 }
diff --git a/src.compiler/kotlin/KotlinAstTransformer.ts b/src.compiler/kotlin/KotlinAstTransformer.ts
index c7540600e..abbb66dfe 100644
--- a/src.compiler/kotlin/KotlinAstTransformer.ts
+++ b/src.compiler/kotlin/KotlinAstTransformer.ts
@@ -10,11 +10,15 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         this._testMethodAttribute = 'org.junit.Test';
     }
 
-    public get extension(): string {
-        return '.kt'
+    public override get extension(): string {
+        return '.kt';
     }
 
 
+    public override get targetTag(): string {
+        return 'kotlin';
+    }
+
     private _paramReferences: Map<string, cs.Identifier[]>[] = [];
     private _paramsWithAssignment: Set<string>[] = [];
 
@@ -22,7 +26,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return 'param' + name;
     }
 
-    protected getIdentifierName(identifier: cs.Identifier, expression: ts.Identifier): string {
+    protected override getIdentifierName(identifier: cs.Identifier, expression: ts.Identifier): string {
         const paramName = super.getIdentifierName(identifier, expression);
         if (
             identifier.tsSymbol &&
@@ -45,7 +49,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return paramName;
     }
 
-    protected visitPrefixUnaryExpression(parent: cs.Node, expression: ts.PrefixUnaryExpression) {
+    protected override visitPrefixUnaryExpression(parent: cs.Node, expression: ts.PrefixUnaryExpression) {
         const pre = super.visitPrefixUnaryExpression(parent, expression);
         if (pre) {
             switch (pre.operator) {
@@ -61,7 +65,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return pre;
     }
 
-    protected visitPostfixUnaryExpression(parent: cs.Node, expression: ts.PostfixUnaryExpression) {
+    protected override visitPostfixUnaryExpression(parent: cs.Node, expression: ts.PostfixUnaryExpression) {
         const post = super.visitPostfixUnaryExpression(parent, expression);
         if (post) {
             switch (post.operator) {
@@ -77,7 +81,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return post;
     }
 
-    protected visitBinaryExpression(parent: cs.Node, expression: ts.BinaryExpression) {
+    protected override visitBinaryExpression(parent: cs.Node, expression: ts.BinaryExpression) {
         const bin = super.visitBinaryExpression(parent, expression);
         // detect parameter assignment
         if (
@@ -98,7 +102,8 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         // a == this or this == a
         // within an equals method needs to have the operator ===
 
-        if (bin && 
+        if (
+            bin &&
             cs.isBinaryExpression(bin) &&
             (expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken ||
                 expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken) &&
@@ -167,7 +172,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         block.statements.unshift(...localParams);
     }
 
-    protected visitGetAccessor(parent: cs.ClassDeclaration, classElement: ts.GetAccessorDeclaration) {
+    protected override visitGetAccessor(parent: cs.ClassDeclaration, classElement: ts.GetAccessorDeclaration) {
         this._paramReferences.push(new Map<string, cs.Identifier[]>());
         this._paramsWithAssignment.push(new Set<string>());
 
@@ -179,7 +184,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return el;
     }
 
-    protected visitSetAccessor(parent: cs.ClassDeclaration, classElement: ts.SetAccessorDeclaration) {
+    protected override visitSetAccessor(parent: cs.ClassDeclaration, classElement: ts.SetAccessorDeclaration) {
         this._paramReferences.push(new Map<string, cs.Identifier[]>());
         this._paramsWithAssignment.push(new Set<string>());
 
@@ -194,7 +199,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return el;
     }
 
-    protected visitConstructorDeclaration(parent: cs.ClassDeclaration, classElement: ts.ConstructorDeclaration) {
+    protected override visitConstructorDeclaration(parent: cs.ClassDeclaration, classElement: ts.ConstructorDeclaration) {
         this._paramReferences.push(new Map<string, cs.Identifier[]>());
         this._paramsWithAssignment.push(new Set<string>());
 
@@ -210,7 +215,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return constr;
     }
 
-    protected visitArrowExpression(parent: cs.Node, expression: ts.ArrowFunction) {
+    protected override visitArrowExpression(parent: cs.Node, expression: ts.ArrowFunction) {
         this._paramReferences.push(new Map<string, cs.Identifier[]>());
         this._paramsWithAssignment.push(new Set<string>());
 
@@ -222,7 +227,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return func;
     }
 
-    protected visitFunctionExpression(parent: cs.Node, expression: ts.FunctionExpression) {
+    protected override visitFunctionExpression(parent: cs.Node, expression: ts.FunctionExpression) {
         this._paramReferences.push(new Map<string, cs.Identifier[]>());
         this._paramsWithAssignment.push(new Set<string>());
 
@@ -234,7 +239,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return func;
     }
 
-    protected visitMethodDeclaration(
+    protected override visitMethodDeclaration(
         parent: cs.ClassDeclaration | cs.InterfaceDeclaration,
         classElement: ts.MethodDeclaration
     ) {
@@ -253,52 +258,22 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return method;
     }
 
-    protected visitPropertyAccessExpression(parent: cs.Node, expression: ts.PropertyAccessExpression) {
+    protected override visitPropertyAccessExpression(parent: cs.Node, expression: ts.PropertyAccessExpression) {
         const base = super.visitPropertyAccessExpression(parent, expression);
 
         return base;
     }
 
-    protected getSymbolName(parentSymbol: ts.Symbol, symbol: ts.Symbol, expression: cs.Expression): string | null {
+    protected override getSymbolName(parentSymbol: ts.Symbol, symbol: ts.Symbol, expression: cs.Expression): string | null {
         switch (parentSymbol.name) {
-            case 'Array':
-                switch (symbol.name) {
-                    case 'length':
-                        // new Array<string>(other.length)
-                        if (expression.parent &&
-                            cs.isNewExpression(expression.parent) &&
-                            (expression.parent.tsNode as ts.NewExpression).arguments?.length === 1 &&
-                            (expression.parent.type as cs.UnresolvedTypeNode).tsType?.symbol
-                                ?.name === 'ArrayConstructor'
-                        ) {
-                            return 'size';
-                        }
-
-                        return 'size.toDouble()';
-                    case 'push':
-                        return 'add';
-                    case 'indexOf':
-                        return 'indexOfInDouble';
-                    case 'filter':
-                        return 'filterBy';
-                    case 'reverse':
-                        return 'rev';
-                    case 'fill':
-                        return 'fillWith';
-                    case 'map':
-                        return 'mapTo';
-                }
-                break;
             case 'String':
                 switch (symbol.name) {
                     case 'length':
                         if (
-                            expression.parent && (
-                            cs.isReturnStatement(expression.parent) ||
-                            cs.isVariableDeclaration(expression.parent) ||
-                            (cs.isBinaryExpression(expression.parent) &&
-                                expression.parent.operator === '=')
-                            )
+                            expression.parent &&
+                            (cs.isReturnStatement(expression.parent) ||
+                                cs.isVariableDeclaration(expression.parent) ||
+                                (cs.isBinaryExpression(expression.parent) && expression.parent.operator === '='))
                         ) {
                             return 'length.toDouble()';
                         }
@@ -314,25 +289,21 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
                         return 'lowercase';
                     case 'toUpperCase':
                         return 'uppercase';
+                    case 'split':
+                        return 'splitBy';
+                }
+                break;
+            case 'Number':
+                switch (symbol.name) {
+                    case 'toString':
+                        return 'toInvariantString';
                 }
                 break;
         }
         return null;
     }
 
-    private isWithinForInitializer(expression: ts.Node): Boolean {
-        if (!expression.parent) {
-            return false;
-        }
-
-        if (ts.isForStatement(expression.parent) && expression.parent.initializer === expression) {
-            return true;
-        }
-
-        return this.isWithinForInitializer(expression.parent!);
-    }
-
-    protected visitNonNullExpression(parent: cs.Node, expression: ts.NonNullExpression) {
+    protected override visitNonNullExpression(parent: cs.Node, expression: ts.NonNullExpression) {
         const nonNullExpression = {
             expression: {} as cs.Expression,
             parent: parent,
@@ -348,7 +319,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         return nonNullExpression;
     }
 
-    protected visitAsExpression(parent: cs.Node, expression: ts.AsExpression): cs.Expression | null {
+    protected override visitAsExpression(parent: cs.Node, expression: ts.AsExpression): cs.Expression | null {
         if (this.isCastToEnum(expression)) {
             const methodAccess = {
                 nodeType: cs.SyntaxKind.MemberAccessExpression,
@@ -383,7 +354,7 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
             } as cs.MemberAccessExpression;
 
             let expr = this.visitExpression(methodAccess, expression.expression);
-            if(!expr) {
+            if (!expr) {
                 return null;
             }
             methodAccess.expression = expr;
@@ -414,4 +385,65 @@ export default class KotlinAstTransformer extends CSharpAstTransformer {
         let targetType = this._context.typeChecker.getTypeFromTypeNode(expression.type);
         return targetType.flags & ts.TypeFlags.Enum || targetType.flags & ts.TypeFlags.EnumLiteral;
     }
+
+    protected override createMapEntry(parent: cs.Node, expression: ts.ArrayLiteralExpression): cs.Expression {
+        const csExpr = {
+            parent: parent,
+            tsNode: expression,
+            nodeType: cs.SyntaxKind.InvocationExpression,
+            arguments: [],
+            expression: {} as cs.Expression
+        } as cs.InvocationExpression;
+
+        let mapEntryTypeName = 'MapEntry';
+        if (expression.elements.length === 2) {
+            const keyType = this._context.getType(expression.elements[0]);
+            let keyTypeContainerName = this.getContainerTypeName(keyType);
+
+            const valueType = this._context.getType(expression.elements[1]);
+            let valueTypeContainerName = this.getContainerTypeName(valueType);
+
+            if (keyTypeContainerName || valueTypeContainerName) {
+                keyTypeContainerName = keyTypeContainerName || 'Object';
+                valueTypeContainerName = valueTypeContainerName || 'Object';
+                mapEntryTypeName = keyTypeContainerName + valueTypeContainerName + mapEntryTypeName;
+            }
+        }
+
+        csExpr.expression = {
+            nodeType: cs.SyntaxKind.Identifier,
+            text: this._context.makeTypeName(`alphaTab.collections.${mapEntryTypeName}`),
+            parent: csExpr,
+            tsNode: expression
+        } as cs.Identifier;
+
+        expression.elements.forEach(e => {
+            const ex = this.visitExpression(csExpr, e);
+            if (ex) {
+                csExpr.arguments.push(ex);
+            }
+        });
+
+        return csExpr;
+    }
+
+    private getContainerTypeName(tsType: ts.Type): string | null {
+        if (this._context.isNullableType(tsType)) {
+            return null;
+        }
+        if (
+            (tsType.flags & ts.TypeFlags.Enum) !== 0 ||
+            (tsType.flags & ts.TypeFlags.EnumLike) !== 0 ||
+            (tsType.flags & ts.TypeFlags.EnumLiteral) !== 0
+        ) {
+            return null;
+        }
+        if ((tsType.flags & ts.TypeFlags.Number) !== 0 || (tsType.flags & ts.TypeFlags.NumberLiteral) !== 0) {
+            return 'Double';
+        }
+        if ((tsType.flags & ts.TypeFlags.Boolean) !== 0 || (tsType.flags & ts.TypeFlags.BooleanLiteral) !== 0) {
+            return 'Boolean';
+        }
+        return null;
+    }
 }
diff --git a/src.compiler/kotlin/KotlinEmitterContext.ts b/src.compiler/kotlin/KotlinEmitterContext.ts
index c30d09cf2..2356bc2aa 100644
--- a/src.compiler/kotlin/KotlinEmitterContext.ts
+++ b/src.compiler/kotlin/KotlinEmitterContext.ts
@@ -8,7 +8,7 @@ export default class KotlinEmitterContext extends CSharpEmitterContext {
         this.noPascalCase = true;
     }
 
-    protected getClassName(type: cs.NamedTypeDeclaration, expr?: cs.Node) {
+    protected override getClassName(type: cs.NamedTypeDeclaration, expr?: cs.Node) {
         let className = super.getClassName(type, expr);
         // partial member access
         if (
@@ -22,7 +22,7 @@ export default class KotlinEmitterContext extends CSharpEmitterContext {
         return className;
     }
 
-    protected toCoreTypeName(s: string) {
+    protected override toCoreTypeName(s: string) {
         if(s === 'String') {
             return 'CoreString';
         }
@@ -40,10 +40,15 @@ export default class KotlinEmitterContext extends CSharpEmitterContext {
         return !!ts.getJSDocTags(tsSymbol.valueDeclaration).find(t => t.tagName.text === 'partial');
     }
 
-    protected isClassElementOverride(classType: ts.Type, classElement: ts.ClassElement) {
-        if (this.hasAnyBaseTypeClassMember(classType, classElement.name!.getText(), true)) {
-            return true;
-        }
+
+    protected override getOverriddenMembers(classType: ts.InterfaceType, classElement: ts.ClassElement): ts.Symbol[] {
+        const symbols: ts.Symbol[] = [];
+        this.collectOverriddenMembersByName(symbols, classType, classElement.name!.getText(), false, true);
+        return symbols;
+    }
+
+    protected override collectOverriddenMembersByName(symbols: ts.Symbol[], classType: ts.InterfaceType, memberName: string, includeOwnMembers: boolean = false, allowInterfaces: boolean = false) {
+        super.collectOverriddenMembersByName(symbols, classType, memberName, includeOwnMembers, allowInterfaces);
 
         if (
             classType.symbol.valueDeclaration && 
@@ -57,9 +62,7 @@ export default class KotlinEmitterContext extends CSharpEmitterContext {
             if (implementsClause) {
                 for (const typeSyntax of implementsClause.types) {
                     const type = this.typeChecker.getTypeFromTypeNode(typeSyntax);
-                    if (this.hasClassMember(type, classElement.name!.getText())) {
-                        return true;
-                    }
+                    super.collectOverriddenMembersByName(symbols, type as ts.InterfaceType, memberName, true, allowInterfaces);
                 }
             }
         }
@@ -67,7 +70,7 @@ export default class KotlinEmitterContext extends CSharpEmitterContext {
         return false;
     }
 
-    public isValueTypeNotNullSmartCast(expression: ts.Expression): boolean | undefined {
+    public override isValueTypeNotNullSmartCast(expression: ts.Expression): boolean | undefined {
         return undefined;
     }
 }
diff --git a/src.compiler/typescript/CloneEmitter.ts b/src.compiler/typescript/CloneEmitter.ts
index 60084332b..01ea069fc 100644
--- a/src.compiler/typescript/CloneEmitter.ts
+++ b/src.compiler/typescript/CloneEmitter.ts
@@ -91,7 +91,9 @@ function generateClonePropertyStatements(
                         [ts.factory.createVariableDeclaration('i')],
                         ts.NodeFlags.Const
                     ),
-                    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('original'), propertyName),
+                    ts.factory.createNonNullExpression(
+                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('original'), propertyName)
+                    ),
                     ts.factory.createBlock([
                         ts.factory.createExpressionStatement(
                             collectionAddMethod
diff --git a/src.compiler/typescript/Serializer.common.ts b/src.compiler/typescript/Serializer.common.ts
new file mode 100644
index 000000000..61733e49f
--- /dev/null
+++ b/src.compiler/typescript/Serializer.common.ts
@@ -0,0 +1,69 @@
+import * as ts from 'typescript';
+import * as path from 'path';
+
+export interface JsonProperty {
+    partialNames: boolean;
+    property: ts.PropertyDeclaration;
+    jsonNames: string[];
+    target?: string;
+}
+
+export interface JsonSerializable {
+    isStrict: boolean;
+    hasToJsonExtension: boolean;
+    hasSetPropertyExtension: boolean;
+    properties: JsonProperty[];
+}
+
+
+export function isImmutable(type: ts.Type | null): boolean {
+    if (!type || !type.symbol) {
+        return false;
+    }
+
+    const declaration = type.symbol.valueDeclaration;
+    if (declaration) {
+        return !!ts.getJSDocTags(declaration).find(t => t.tagName.text === 'json_immutable');
+    }
+
+    return false;
+}
+
+export function createStringUnknownMapNode(): ts.TypeNode {
+    return ts.factory.createTypeReferenceNode('Map', [
+        ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
+        ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
+    ]);
+}
+
+function removeExtension(fileName: string) {
+    return fileName.substring(0, fileName.lastIndexOf('.'));
+}
+
+export function toImportPath(fileName: string) {
+    return '@' + removeExtension(fileName).split('\\').join('/');
+}
+
+export function findModule(type: ts.Type, options: ts.CompilerOptions) {
+    if (type.symbol && type.symbol.declarations) {
+        for (const decl of type.symbol.declarations) {
+            const file = decl.getSourceFile();
+            if (file) {
+                const relative = path.relative(path.join(path.resolve(options.baseUrl!)), path.resolve(file.fileName));
+                return toImportPath(relative);
+            }
+        }
+
+        return './' + type.symbol.name;
+    }
+
+    return '';
+}
+
+export function findSerializerModule(type: ts.Type, options: ts.CompilerOptions) {
+    let module = findModule(type, options);
+    const importPath = module.split('/');
+    importPath.splice(1, 0, 'generated');
+    importPath[importPath.length - 1] = type.symbol!.name + 'Serializer';
+    return importPath.join('/');
+}
diff --git a/src.compiler/typescript/Serializer.fromJson.ts b/src.compiler/typescript/Serializer.fromJson.ts
new file mode 100644
index 000000000..f1b5e5299
--- /dev/null
+++ b/src.compiler/typescript/Serializer.fromJson.ts
@@ -0,0 +1,36 @@
+import * as ts from 'typescript';
+import { addNewLines, createNodeFromSource, setMethodBody } from '../BuilderHelpers';
+import { JsonSerializable } from './Serializer.common';
+
+function generateFromJsonBody(serializable: JsonSerializable, importer: (name: string, module: string) => void) {
+    importer('JsonHelper', '@src/io/JsonHelper');
+    return ts.factory.createBlock(addNewLines([
+        createNodeFromSource<ts.IfStatement>(`if(!m) { 
+            return; 
+        }`, ts.SyntaxKind.IfStatement),
+        serializable.isStrict
+            ? createNodeFromSource<ts.ExpressionStatement>(
+                `JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v));`,
+                ts.SyntaxKind.ExpressionStatement
+            )
+            : createNodeFromSource<ts.ExpressionStatement>(
+                `JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v));`,
+                ts.SyntaxKind.ExpressionStatement
+            )
+    ]));
+}
+
+export function createFromJsonMethod(
+    input: ts.ClassDeclaration,
+    serializable: JsonSerializable,
+    importer: (name: string, module: string) => void
+) {
+    const methodDecl = createNodeFromSource<ts.MethodDeclaration>(
+        `public class Serializer {
+            public static fromJson(obj: ${input.name!.text}, m: unknown): void {
+            }
+        }`,
+        ts.SyntaxKind.MethodDeclaration
+    );
+    return setMethodBody(methodDecl, generateFromJsonBody(serializable, importer));
+}
diff --git a/src.compiler/typescript/Serializer.setProperty.ts b/src.compiler/typescript/Serializer.setProperty.ts
new file mode 100644
index 000000000..007340da0
--- /dev/null
+++ b/src.compiler/typescript/Serializer.setProperty.ts
@@ -0,0 +1,570 @@
+import * as ts from 'typescript';
+import { addNewLines, createNodeFromSource, setMethodBody } from '../BuilderHelpers';
+import { isPrimitiveType } from '../BuilderHelpers';
+import { hasFlag } from '../BuilderHelpers';
+import { getTypeWithNullableInfo } from '../BuilderHelpers';
+import { isTypedArray } from '../BuilderHelpers';
+import { unwrapArrayItemType } from '../BuilderHelpers';
+import { isMap } from '../BuilderHelpers';
+import { isEnumType } from '../BuilderHelpers';
+import { isNumberType } from '../BuilderHelpers';
+import { wrapToNonNull } from '../BuilderHelpers';
+import {
+    createStringUnknownMapNode,
+    findModule,
+    findSerializerModule,
+    isImmutable,
+    JsonProperty,
+    JsonSerializable
+} from './Serializer.common';
+
+function isPrimitiveFromJson(type: ts.Type, typeChecker: ts.TypeChecker) {
+    if (!type) {
+        return false;
+    }
+
+    const isArray = isTypedArray(type);
+    const arrayItemType = unwrapArrayItemType(type, typeChecker);
+
+    if (hasFlag(type, ts.TypeFlags.Unknown)) {
+        return true;
+    }
+    if (hasFlag(type, ts.TypeFlags.Number)) {
+        return true;
+    }
+    if (hasFlag(type, ts.TypeFlags.String)) {
+        return true;
+    }
+    if (hasFlag(type, ts.TypeFlags.Boolean)) {
+        return true;
+    }
+
+    if (arrayItemType) {
+        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Number)) {
+            return true;
+        }
+        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.String)) {
+            return true;
+        }
+        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Boolean)) {
+            return true;
+        }
+    } else if (type.symbol) {
+        switch (type.symbol.name) {
+            case 'Uint8Array':
+            case 'Uint16Array':
+            case 'Uint32Array':
+            case 'Int8Array':
+            case 'Int16Array':
+            case 'Int32Array':
+            case 'Float32Array':
+            case 'Float64Array':
+                return true;
+        }
+    }
+
+    return null;
+}
+
+function cloneTypeNode(node: ts.TypeNode): ts.TypeNode {
+    if (ts.isUnionTypeNode(node)) {
+        return ts.factory.createUnionTypeNode(node.types.map(cloneTypeNode));
+    } else if (
+        node.kind === ts.SyntaxKind.StringKeyword ||
+        node.kind === ts.SyntaxKind.NumberKeyword ||
+        node.kind === ts.SyntaxKind.BooleanKeyword ||
+        node.kind === ts.SyntaxKind.UnknownKeyword ||
+        node.kind === ts.SyntaxKind.AnyKeyword ||
+        node.kind === ts.SyntaxKind.VoidKeyword
+    ) {
+        return ts.factory.createKeywordTypeNode(node.kind);
+    } else if (ts.isLiteralTypeNode(node)) {
+        return ts.factory.createLiteralTypeNode(node.literal);
+    } else if (ts.isArrayTypeNode(node)) {
+        return ts.factory.createArrayTypeNode(cloneTypeNode(node.elementType));
+    }
+
+    throw new Error(`Unsupported TypeNode: '${ts.SyntaxKind[node.kind]}' extend type node cloning`);
+}
+
+function generateSetPropertyBody(
+    program: ts.Program,
+    serializable: JsonSerializable,
+    importer: (name: string, module: string) => void
+) {
+    const statements: ts.Statement[] = [];
+    const cases: ts.CaseOrDefaultClause[] = [];
+
+    const typeChecker = program.getTypeChecker();
+    for (const prop of serializable.properties) {
+        const jsonNames = prop.jsonNames.map(j => j.toLowerCase());
+        const caseValues: string[] = jsonNames.filter(j => j !== '');
+        const fieldName = (prop.property.name as ts.Identifier).text;
+
+        const caseStatements: ts.Statement[] = [];
+
+        const type = getTypeWithNullableInfo(typeChecker, prop.property.type);
+
+        const assignField = function (expr: ts.Expression): ts.Statement {
+            return ts.factory.createExpressionStatement(
+                ts.factory.createAssignment(
+                    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('obj'), fieldName),
+                    expr
+                )
+            );
+        };
+
+        if (isPrimitiveFromJson(type.type!, typeChecker)) {
+            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 (isEnumType(type.type)) {
+            importer(type.type.symbol!.name, findModule(type.type, program.getCompilerOptions()));
+            importer('JsonHelper', '@src/io/JsonHelper');
+            if (type.isNullable) {
+                caseStatements.push(
+                    createNodeFromSource<ts.ExpressionStatement>(
+                        `obj.${fieldName} = JsonHelper.parseEnum<${type.type.symbol.name}>(v, ${type.type.symbol.name});`,
+                        ts.SyntaxKind.ExpressionStatement
+                    )
+                );
+            } else {
+                caseStatements.push(
+                    createNodeFromSource<ts.ExpressionStatement>(
+                        `obj.${fieldName} = JsonHelper.parseEnum<${type.type.symbol.name}>(v, ${type.type.symbol.name})!;`,
+                        ts.SyntaxKind.ExpressionStatement
+                    )
+                );
+            }
+            caseStatements.push(ts.factory.createReturnStatement(ts.factory.createTrue()));
+        } else if (isTypedArray(type.type!)) {
+            const arrayItemType = unwrapArrayItemType(type.type!, typeChecker)!;
+            const collectionAddMethod =
+                (ts
+                    .getJSDocTags(prop.property)
+                    .filter(t => t.tagName.text === 'json_add')
+                    .map(t => t.comment ?? '')[0] as string) ?? `${fieldName}.push`;
+
+            // obj.fieldName = [];
+            // for(const i of value) {
+            //    obj.addFieldName(Type.FromJson(i));
+            // }
+            // or
+            // for(const __li of value) {
+            //    obj.fieldName.push(Type.FromJson(__li));
+            // }
+
+            let itemSerializer = arrayItemType.symbol.name + 'Serializer';
+            importer(itemSerializer, findSerializerModule(arrayItemType, program.getCompilerOptions()));
+            importer(arrayItemType.symbol.name, findModule(arrayItemType, program.getCompilerOptions()));
+
+            const loopItems = [
+                createNodeFromSource<ts.ExpressionStatement>(
+                    `obj.${fieldName} = [];`,
+                    ts.SyntaxKind.ExpressionStatement
+                ),
+                createNodeFromSource<ts.ForOfStatement>(
+                    `for(const o of (v as (Map<string, unknown> | null)[])) {
+                        const i = new ${arrayItemType.symbol.name}();
+                        ${itemSerializer}.fromJson(i, o);
+                        obj.${collectionAddMethod}(i)
+                    }`,
+                    ts.SyntaxKind.ForOfStatement
+                )
+            ];
+
+            if (type.isNullable) {
+                caseStatements.push(
+                    ts.factory.createIfStatement(ts.factory.createIdentifier('v'), ts.factory.createBlock(loopItems))
+                );
+            } else {
+                caseStatements.push(...loopItems);
+            }
+            caseStatements.push(ts.factory.createReturnStatement(ts.factory.createTrue()));
+        } else if (isMap(type.type)) {
+            const mapType = type.type as ts.TypeReference;
+            if (!isPrimitiveType(mapType.typeArguments![0])) {
+                throw new Error('only Map<EnumType, *> maps are supported extend if needed!');
+            }
+
+            let mapKey: ts.Expression;
+            if (isEnumType(mapType.typeArguments![0])) {
+                importer(
+                    mapType.typeArguments![0].symbol!.name,
+                    findModule(mapType.typeArguments![0], program.getCompilerOptions())
+                );
+                importer('JsonHelper', '@src/io/JsonHelper');
+                mapKey = createNodeFromSource<ts.NonNullExpression>(
+                    `JsonHelper.parseEnum<${mapType.typeArguments![0].symbol!.name}>(k, ${mapType.typeArguments![0].symbol!.name
+                    })!`,
+                    ts.SyntaxKind.NonNullExpression
+                );
+            } else if (isNumberType(mapType.typeArguments![0])) {
+                mapKey = createNodeFromSource<ts.CallExpression>(`parseInt(k)`, ts.SyntaxKind.CallExpression);
+            } else {
+                mapKey = ts.factory.createIdentifier('k');
+            }
+
+            let mapValue;
+            let itemSerializer: string = '';
+            if (isPrimitiveFromJson(mapType.typeArguments![1], typeChecker)) {
+                mapValue = ts.factory.createAsExpression(
+                    ts.factory.createIdentifier('v'),
+                    ts.isTypeReferenceNode(prop.property.type!) && prop.property.type.typeArguments
+                        ? cloneTypeNode(prop.property.type.typeArguments[1])
+                        : ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)
+                );
+            } else {
+                itemSerializer = mapType.typeArguments![1].symbol.name + 'Serializer';
+                importer(itemSerializer, findSerializerModule(mapType.typeArguments![1], program.getCompilerOptions()));
+                importer(
+                    mapType.typeArguments![1]!.symbol.name,
+                    findModule(mapType.typeArguments![1], program.getCompilerOptions())
+                );
+                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';
+
+            caseStatements.push(
+                assignField(
+                    ts.factory.createNewExpression(
+                        ts.factory.createIdentifier('Map'),
+                        [
+                            typeChecker.typeToTypeNode(mapType.typeArguments![0], undefined, undefined)!,
+                            typeChecker.typeToTypeNode(mapType.typeArguments![1], undefined, undefined)!
+                        ],
+                        []
+                    )
+                )
+            );
+
+            caseStatements.push(
+                ts.factory.createExpressionStatement(
+                    ts.factory.createCallExpression(
+                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('JsonHelper'), 'forEach'),
+                        undefined,
+                        [
+                            ts.factory.createIdentifier('v'),
+                            ts.factory.createArrowFunction(
+                                undefined,
+                                undefined,
+                                [
+                                    ts.factory.createParameterDeclaration(undefined, undefined, undefined, 'v'),
+                                    ts.factory.createParameterDeclaration(undefined, undefined, undefined, 'k')
+                                ],
+                                undefined,
+                                ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
+                                ts.factory.createBlock(
+                                    addNewLines(
+                                        [
+                                            itemSerializer.length > 0 &&
+                                            createNodeFromSource<ts.VariableStatement>(
+                                                `const i = new ${mapType.typeArguments![1].symbol.name}();`,
+                                                ts.SyntaxKind.VariableStatement
+                                            ),
+                                            itemSerializer.length > 0 &&
+                                            createNodeFromSource<ts.ExpressionStatement>(
+                                                `${itemSerializer}.fromJson(i, v as Map<string, unknown>)`,
+                                                ts.SyntaxKind.ExpressionStatement
+                                            ),
+                                            ts.factory.createExpressionStatement(
+                                                ts.factory.createCallExpression(
+                                                    collectionAddMethod
+                                                        ? ts.factory.createPropertyAccessExpression(
+                                                            ts.factory.createIdentifier('obj'),
+                                                            collectionAddMethod
+                                                        )
+                                                        : ts.factory.createPropertyAccessExpression(
+                                                            ts.factory.createPropertyAccessExpression(
+                                                                ts.factory.createIdentifier('obj'),
+                                                                ts.factory.createIdentifier(fieldName)
+                                                            ),
+                                                            ts.factory.createIdentifier('set')
+                                                        ),
+                                                    undefined,
+                                                    [mapKey, mapValue]
+                                                )
+                                            )
+                                        ].filter(s => !!s) as ts.Statement[]
+                                    )
+                                )
+                            )
+                        ]
+                    )
+                )
+            );
+
+            caseStatements.push(ts.factory.createReturnStatement(ts.factory.createTrue()));
+        } else if (isImmutable(type.type)) {
+            let itemSerializer = type.type.symbol.name;
+            importer(itemSerializer, findModule(type.type, program.getCompilerOptions()));
+
+            // obj.fieldName = TypeName.fromJson(value)!
+            // return true;
+            caseStatements.push(
+                createNodeFromSource<ts.ExpressionStatement>(
+                    `obj.${fieldName} = ${itemSerializer}.fromJson(v)!;`,
+                    ts.SyntaxKind.ExpressionStatement
+                )
+            );
+            caseStatements.push(
+                createNodeFromSource<ts.ReturnStatement>(
+                    `return true;`,
+                    ts.SyntaxKind.ReturnStatement
+                )
+            );
+        } else {
+            // for complex types it is a bit more tricky
+            // if the property matches exactly, we use fromJson
+            // if the property starts with the field name, we try to set a sub-property
+            const jsonNameArray = ts.factory.createArrayLiteralExpression(
+                jsonNames.map(n => ts.factory.createStringLiteral(n))
+            );
+
+            let itemSerializer = type.type.symbol.name + 'Serializer';
+            importer(itemSerializer, findSerializerModule(type.type, program.getCompilerOptions()));
+            if (type.isNullable) {
+                importer(type.type.symbol!.name, findModule(type.type, program.getCompilerOptions()));
+            }
+
+            // TODO if no partial name support, simply generate cases
+            statements.push(
+                ts.factory.createIfStatement(
+                    // if(["", "core"].indexOf(property) >= 0)
+                    ts.factory.createBinaryExpression(
+                        ts.factory.createCallExpression(
+                            ts.factory.createPropertyAccessExpression(jsonNameArray, 'indexOf'),
+                            [],
+                            [ts.factory.createIdentifier('property')]
+                        ),
+                        ts.SyntaxKind.GreaterThanEqualsToken,
+                        ts.factory.createNumericLiteral('0')
+                    ),
+                    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.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[]
+                                        )
+                                    )
+                                ])
+                            )
+                        ])
+                )
+            );
+        }
+
+        if (caseStatements.length > 0) {
+            for (let i = 0; i < caseValues.length; i++) {
+                let caseClause = ts.factory.createCaseClause(
+                    ts.factory.createStringLiteral(caseValues[i]),
+                    // last case gets the statements, others are fall through
+                    i < caseValues.length - 1 ? [] : caseStatements
+                );
+                if (prop.target && i === 0) {
+                    caseClause = ts.addSyntheticLeadingComment(
+                        caseClause,
+                        ts.SyntaxKind.MultiLineCommentTrivia,
+                        `@target ${prop.target}`,
+                        true
+                    );
+                }
+                cases.push(caseClause);
+            }
+        }
+    }
+
+    if (cases.length > 0) {
+        const switchExpr = ts.factory.createSwitchStatement(
+            ts.factory.createIdentifier('property'),
+            ts.factory.createCaseBlock(cases)
+        );
+        statements.unshift(switchExpr);
+    }
+
+    if(serializable.hasSetPropertyExtension) {
+        statements.push(ts.factory.createReturnStatement(
+            createNodeFromSource<ts.CallExpression>(
+                `obj.setProperty(property, v);`,
+                ts.SyntaxKind.CallExpression
+            )
+        ));
+    }
+    else {
+        statements.push(ts.factory.createReturnStatement(ts.factory.createFalse()));
+    }
+
+
+    return ts.factory.createBlock(addNewLines(statements));
+}
+
+export function createSetPropertyMethod(
+    program: ts.Program,
+    input: ts.ClassDeclaration,
+    serializable: JsonSerializable,
+    importer: (name: string, module: string) => void
+) {
+    const methodDecl = createNodeFromSource<ts.MethodDeclaration>(
+        `public class Serializer {
+            public static setProperty(obj: ${input.name!.text}, property: string, v: unknown): boolean {
+            }
+        }`,
+        ts.SyntaxKind.MethodDeclaration
+    );
+    return setMethodBody(methodDecl, generateSetPropertyBody(program, serializable, importer));
+}
diff --git a/src.compiler/typescript/Serializer.toJson.ts b/src.compiler/typescript/Serializer.toJson.ts
new file mode 100644
index 000000000..40a4757e9
--- /dev/null
+++ b/src.compiler/typescript/Serializer.toJson.ts
@@ -0,0 +1,270 @@
+import * as ts from 'typescript';
+import {
+    addNewLines,
+    createNodeFromSource,
+    setMethodBody,
+    isPrimitiveType,
+    hasFlag,
+    getTypeWithNullableInfo,
+    isTypedArray,
+    unwrapArrayItemType,
+    isMap,
+    isEnumType
+} from '../BuilderHelpers';
+import { findModule, findSerializerModule, isImmutable, JsonProperty, JsonSerializable } from './Serializer.common';
+
+function isPrimitiveToJson(type: ts.Type, typeChecker: ts.TypeChecker) {
+    if (!type) {
+        return false;
+    }
+
+    const isArray = isTypedArray(type);
+    const arrayItemType = unwrapArrayItemType(type, typeChecker);
+
+    if (hasFlag(type, ts.TypeFlags.Unknown)) {
+        return true;
+    }
+    if (hasFlag(type, ts.TypeFlags.Number)) {
+        return true;
+    }
+    if (hasFlag(type, ts.TypeFlags.String)) {
+        return true;
+    }
+    if (hasFlag(type, ts.TypeFlags.Boolean)) {
+        return true;
+    }
+
+    if (arrayItemType) {
+        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Number)) {
+            return true;
+        }
+        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.String)) {
+            return true;
+        }
+        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Boolean)) {
+            return true;
+        }
+    } else if (type.symbol) {
+        switch (type.symbol.name) {
+            case 'Uint8Array':
+            case 'Uint16Array':
+            case 'Uint32Array':
+            case 'Int8Array':
+            case 'Int16Array':
+            case 'Int32Array':
+            case 'Float32Array':
+            case 'Float64Array':
+                return true;
+        }
+    }
+
+    return false;
+}
+
+function generateToJsonBody(
+    program: ts.Program,
+    serializable: JsonSerializable,
+    importer: (name: string, module: string) => void
+) {
+    const statements: ts.Statement[] = [];
+
+    statements.push(
+        createNodeFromSource<ts.IfStatement>(
+            `
+            if(!obj) {
+                return null;
+            }
+        `,
+            ts.SyntaxKind.IfStatement
+        )
+    );
+
+    statements.push(
+        createNodeFromSource<ts.VariableStatement>(
+            `
+            const o = new Map<string, unknown>();
+        `,
+            ts.SyntaxKind.VariableStatement
+        )
+    );
+
+    for (let prop of serializable.properties) {
+        const fieldName = (prop.property.name as ts.Identifier).text;
+        const jsonName = prop.jsonNames.filter(n => n !== '')[0];
+
+        if (!jsonName) {
+            continue;
+        }
+        const typeChecker = program.getTypeChecker();
+        const type = getTypeWithNullableInfo(typeChecker, prop.property.type!);
+        const isArray = isTypedArray(type.type!);
+
+        let propertyStatements: ts.Statement[] = [];
+
+        if (isPrimitiveToJson(type.type!, typeChecker)) {
+            propertyStatements.push(
+                createNodeFromSource<ts.ExpressionStatement>(
+                    `
+                    o.set(${JSON.stringify(jsonName)}, obj.${fieldName});
+                `,
+                    ts.SyntaxKind.ExpressionStatement
+                )
+            );
+        } else if (isEnumType(type.type!)) {
+            if (type.isNullable) {
+                propertyStatements.push(
+                    createNodeFromSource<ts.ExpressionStatement>(
+                        `
+                        o.set(${JSON.stringify(jsonName)}, obj.${fieldName} as number|null);
+                    `,
+                        ts.SyntaxKind.ExpressionStatement
+                    )
+                );
+            } else {
+                propertyStatements.push(
+                    createNodeFromSource<ts.ExpressionStatement>(
+                        `
+                        o.set(${JSON.stringify(jsonName)}, obj.${fieldName} as number);
+                    `,
+                        ts.SyntaxKind.ExpressionStatement
+                    )
+                );
+            }
+        } else if (isArray) {
+            const arrayItemType = unwrapArrayItemType(type.type!, typeChecker)!;
+            let itemSerializer = arrayItemType.symbol.name + 'Serializer';
+            importer(itemSerializer, findSerializerModule(arrayItemType, program.getCompilerOptions()));
+            if (type.isNullable) {
+                propertyStatements.push(
+                    createNodeFromSource<ts.IfStatement>(
+                        `if(obj.${fieldName} !== null) {
+                            o.set(${JSON.stringify(jsonName)}, obj.${fieldName}?.map(i => ${itemSerializer}.toJson(i)));
+                        }`,
+                        ts.SyntaxKind.IfStatement
+                    )
+                );
+            } else {
+                propertyStatements.push(
+                    createNodeFromSource<ts.ExpressionStatement>(
+                        `
+                        o.set(${JSON.stringify(jsonName)}, obj.${fieldName}.map(i => ${itemSerializer}.toJson(i)));
+                    `,
+                        ts.SyntaxKind.ExpressionStatement
+                    )
+                );
+            }
+
+        } else if (isMap(type.type)) {
+            const mapType = type.type as ts.TypeReference;
+            if (!isPrimitiveType(mapType.typeArguments![0])) {
+                throw new Error('only Map<Primitive, *> maps are supported extend if needed!');
+            }
+
+            let serializeBlock: ts.Block;
+            if (isPrimitiveToJson(mapType.typeArguments![1], typeChecker)) {
+                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 if (isEnumType(mapType.typeArguments![1])) {
+                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 as number);
+                    }
+                }`, ts.SyntaxKind.Block);
+            } else {
+                const itemSerializer = mapType.typeArguments![1].symbol.name + 'Serializer';
+                importer(itemSerializer, findSerializerModule(mapType.typeArguments![1], program.getCompilerOptions()));
+
+                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(), ${itemSerializer}.toJson(v));
+                    }
+                }`, ts.SyntaxKind.Block);
+            }
+
+            if (type.isNullable) {
+                propertyStatements.push(ts.factory.createIfStatement(
+                    ts.factory.createBinaryExpression(
+                        ts.factory.createPropertyAccessExpression(
+                            ts.factory.createIdentifier('obj'),
+                            fieldName
+                        ),
+                        ts.SyntaxKind.ExclamationEqualsEqualsToken,
+                        ts.factory.createNull()
+                    ),
+                    serializeBlock)
+                );
+            } else {
+                propertyStatements.push(serializeBlock);
+            }
+        } else if (isImmutable(type.type)) {
+            let itemSerializer = type.type.symbol.name;
+            importer(itemSerializer, findModule(type.type, program.getCompilerOptions()));
+            propertyStatements.push(
+                createNodeFromSource<ts.ExpressionStatement>(
+                    `
+                    o.set(${JSON.stringify(jsonName)}, ${itemSerializer}.toJson(obj.${fieldName}));
+                `,
+                    ts.SyntaxKind.ExpressionStatement
+                )
+            );
+        } else {
+            let itemSerializer = type.type.symbol.name + 'Serializer';
+            importer(itemSerializer, findSerializerModule(type.type, program.getCompilerOptions()));
+            propertyStatements.push(
+                createNodeFromSource<ts.ExpressionStatement>(
+                    `
+                    o.set(${JSON.stringify(jsonName)}, ${itemSerializer}.toJson(obj.${fieldName}));
+                `,
+                    ts.SyntaxKind.ExpressionStatement
+                )
+            );
+        }
+
+        if (prop.target) {
+            propertyStatements = propertyStatements.map(s =>
+                ts.addSyntheticLeadingComment(s, ts.SyntaxKind.MultiLineCommentTrivia, `@target ${prop.target}`, true)
+            );
+        }
+
+        statements.push(...propertyStatements);
+    }
+
+    if(serializable.hasToJsonExtension) {
+        statements.push( createNodeFromSource<ts.ExpressionStatement>(
+            `obj.toJson(o);`,
+            ts.SyntaxKind.ExpressionStatement
+        ));
+    }
+    
+    statements.push(ts.factory.createReturnStatement(ts.factory.createIdentifier('o')));
+
+    return ts.factory.createBlock(addNewLines(statements));
+}
+
+export function createToJsonMethod(
+    program: ts.Program,
+    input: ts.ClassDeclaration,
+    serializable: JsonSerializable,
+    importer: (name: string, module: string) => void
+) {
+    const methodDecl = createNodeFromSource<ts.MethodDeclaration>(
+        `public class Serializer {
+            public static toJson(obj: ${input.name!.text} | null): Map<string, unknown> | null {
+            }
+        }`,
+        ts.SyntaxKind.MethodDeclaration
+    );
+    return setMethodBody(methodDecl, generateToJsonBody(program, serializable, importer));
+}
diff --git a/src.compiler/typescript/SerializerEmitter.ts b/src.compiler/typescript/SerializerEmitter.ts
index 48d47c6ec..a3492d89d 100644
--- a/src.compiler/typescript/SerializerEmitter.ts
+++ b/src.compiler/typescript/SerializerEmitter.ts
@@ -6,1219 +6,11 @@
 import * as path from 'path';
 import * as ts from 'typescript';
 import createEmitter from './EmitterBase';
-import { addNewLines } from '../BuilderHelpers';
-import { isPrimitiveType } from '../BuilderHelpers';
-import { hasFlag } from '../BuilderHelpers';
-import { getTypeWithNullableInfo } from '../BuilderHelpers';
-import { isTypedArray } from '../BuilderHelpers';
-import { unwrapArrayItemType } from '../BuilderHelpers';
-import { isMap } from '../BuilderHelpers';
-import { isEnumType } from '../BuilderHelpers';
-import { isNumberType } from '../BuilderHelpers';
-import { wrapToNonNull } from '../BuilderHelpers';
+import { JsonProperty, JsonSerializable, toImportPath } from './Serializer.common';
+import { createSetPropertyMethod } from './Serializer.setProperty';
+import { createFromJsonMethod } from './Serializer.fromJson';
+import { createToJsonMethod } from './Serializer.toJson';
 
-interface JsonProperty {
-    partialNames: boolean;
-    property: ts.PropertyDeclaration;
-    jsonNames: string[];
-    target?: string;
-}
-
-function isImmutable(type: ts.Type | null): boolean {
-    if (!type || !type.symbol) {
-        return false;
-    }
-
-    const declaration = type.symbol.valueDeclaration;
-    if (declaration) {
-        return !!ts.getJSDocTags(declaration).find(t => t.tagName.text === 'json_immutable');
-    }
-
-    return false;
-}
-
-function removeExtension(fileName: string) {
-    return fileName.substring(0, fileName.lastIndexOf('.'));
-}
-
-function toImportPath(fileName: string) {
-    return '@' + removeExtension(fileName).split('\\').join('/');
-}
-
-function createStringUnknownMapNode(): ts.TypeNode {
-    return ts.factory.createTypeReferenceNode('Map', [
-        ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
-        ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
-    ]);
-}
-
-function findModule(type: ts.Type, options: ts.CompilerOptions) {
-    if (type.symbol && type.symbol.declarations) {
-        for (const decl of type.symbol.declarations) {
-            const file = decl.getSourceFile();
-            if (file) {
-                const relative = path.relative(path.join(path.resolve(options.baseUrl!)), path.resolve(file.fileName));
-                return toImportPath(relative);
-            }
-        }
-
-        return './' + type.symbol.name;
-    }
-
-    return '';
-}
-
-function findSerializerModule(type: ts.Type, options: ts.CompilerOptions) {
-    let module = findModule(type, options);
-    const importPath = module.split('/');
-    importPath.splice(1, 0, 'generated');
-    importPath[importPath.length - 1] = type.symbol!.name + 'Serializer';
-    return importPath.join('/');
-}
-
-//
-// fromJson
-function generateFromJsonBody(importer: (name: string, module: string) => void) {
-    importer('JsonHelper', '@src/io/JsonHelper');
-    return ts.factory.createBlock(
-        addNewLines([
-            ts.factory.createIfStatement(
-                ts.factory.createPrefixUnaryExpression(
-                    ts.SyntaxKind.ExclamationToken,
-                    ts.factory.createIdentifier('m')
-                ),
-                ts.factory.createBlock([ts.factory.createReturnStatement()])
-            ),
-            ts.factory.createExpressionStatement(
-                ts.factory.createCallExpression(
-                    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('JsonHelper'), 'forEach'),
-                    undefined,
-                    [
-                        ts.factory.createIdentifier('m'),
-                        ts.factory.createArrowFunction(
-                            undefined,
-                            undefined,
-                            [
-                                ts.factory.createParameterDeclaration(undefined, undefined, undefined, 'v'),
-                                ts.factory.createParameterDeclaration(undefined, undefined, undefined, 'k')
-                            ],
-                            undefined,
-                            ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
-                            ts.factory.createCallExpression(
-                                ts.factory.createPropertyAccessExpression(ts.factory.createThis(), 'setProperty'),
-                                undefined,
-                                [
-                                    ts.factory.createIdentifier('obj'),
-                                    ts.factory.createCallExpression(
-                                        ts.factory.createPropertyAccessExpression(
-                                            ts.factory.createIdentifier('k'),
-                                            'toLowerCase'
-                                        ),
-                                        undefined,
-                                        []
-                                    ),
-                                    ts.factory.createIdentifier('v')
-                                ]
-                            )
-                        )
-                    ]
-                )
-            )
-        ])
-    );
-}
-
-function createFromJsonMethod(input: ts.ClassDeclaration, importer: (name: string, module: string) => void) {
-    return ts.factory.createMethodDeclaration(
-        undefined,
-        [
-            ts.factory.createModifier(ts.SyntaxKind.PublicKeyword),
-            ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)
-        ],
-        undefined,
-        'fromJson',
-        undefined,
-        undefined,
-        [
-            ts.factory.createParameterDeclaration(
-                undefined,
-                undefined,
-                undefined,
-                'obj',
-                undefined,
-                ts.factory.createTypeReferenceNode(input.name!.text, undefined)
-            ),
-            ts.factory.createParameterDeclaration(
-                undefined,
-                undefined,
-                undefined,
-                'm',
-                undefined,
-                ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
-            )
-        ],
-        ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword),
-        generateFromJsonBody(importer)
-    );
-}
-//
-// toJson
-function isPrimitiveToJson(type: ts.Type, typeChecker: ts.TypeChecker) {
-    if (!type) {
-        return false;
-    }
-
-    const isArray = isTypedArray(type);
-    const arrayItemType = unwrapArrayItemType(type, typeChecker);
-
-    if (hasFlag(type, ts.TypeFlags.Unknown)) {
-        return true;
-    }
-    if (hasFlag(type, ts.TypeFlags.Number)) {
-        return true;
-    }
-    if (hasFlag(type, ts.TypeFlags.String)) {
-        return true;
-    }
-    if (hasFlag(type, ts.TypeFlags.Boolean)) {
-        return 'val';
-    }
-
-    if (arrayItemType) {
-        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Number)) {
-            return true;
-        }
-        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.String)) {
-            return true;
-        }
-        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Boolean)) {
-            return true;
-        }
-    } else if (type.symbol) {
-        switch (type.symbol.name) {
-            case 'Uint8Array':
-            case 'Uint16Array':
-            case 'Uint32Array':
-            case 'Int8Array':
-            case 'Int16Array':
-            case 'Int32Array':
-            case 'Float32Array':
-            case 'Float64Array':
-                return true;
-        }
-    }
-
-    return false;
-}
-
-function generateToJsonBody(
-    program: ts.Program,
-    propertiesToSerialize: JsonProperty[],
-    importer: (name: string, module: string) => void
-) {
-    const statements: ts.Statement[] = [];
-
-    statements.push(
-        ts.factory.createIfStatement(
-            ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, ts.factory.createIdentifier('obj')),
-            ts.factory.createBlock([ts.factory.createReturnStatement(ts.factory.createNull())])
-        )
-    );
-
-    statements.push(
-        ts.factory.createVariableStatement(
-            undefined,
-            ts.factory.createVariableDeclarationList(
-                [
-                    ts.factory.createVariableDeclaration(
-                        'o',
-                        undefined,
-                        undefined,
-                        ts.factory.createNewExpression(
-                            ts.factory.createIdentifier('Map'),
-                            [
-                                ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
-                                ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
-                            ],
-                            []
-                        )
-                    )
-                ],
-                ts.NodeFlags.Const
-            )
-        )
-    );
-
-    for (let prop of propertiesToSerialize) {
-        const fieldName = (prop.property.name as ts.Identifier).text;
-        const jsonName = prop.jsonNames.filter(n => n !== '')[0];
-
-        if (!jsonName) {
-            continue;
-        }
-        const typeChecker = program.getTypeChecker();
-        const type = getTypeWithNullableInfo(typeChecker, prop.property.type!);
-        const isArray = isTypedArray(type.type!);
-
-        let propertyStatements: ts.Statement[] = [];
-
-        if (isPrimitiveToJson(type.type!, typeChecker)) {
-            propertyStatements.push(
-                ts.factory.createExpressionStatement(
-                    ts.factory.createCallExpression(
-                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('o'), 'set'),
-                        undefined,
-                        [
-                            ts.factory.createStringLiteral(jsonName),
-                            ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('obj'), fieldName)
-                        ]
-                    )
-                )
-            );
-        } else if (isEnumType(type.type!)) {
-            propertyStatements.push(
-                ts.factory.createExpressionStatement(
-                    ts.factory.createCallExpression(
-                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('o'), 'set'),
-                        undefined,
-                        [
-                            ts.factory.createStringLiteral(jsonName),
-                            ts.factory.createAsExpression(
-                                ts.factory.createPropertyAccessExpression(
-                                    ts.factory.createIdentifier('obj'),
-                                    fieldName
-                                ),
-                                type.isNullable
-                                    ? ts.factory.createUnionTypeNode([
-                                          ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
-                                          ts.factory.createLiteralTypeNode(ts.factory.createNull())
-                                      ])
-                                    : ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
-                            )
-                        ]
-                    )
-                )
-            );
-        } else if (isArray) {
-            const arrayItemType = unwrapArrayItemType(type.type!, typeChecker)!;
-            let itemSerializer = arrayItemType.symbol.name + 'Serializer';
-            importer(itemSerializer, findSerializerModule(arrayItemType, program.getCompilerOptions()));
-
-            propertyStatements.push(
-                ts.factory.createExpressionStatement(
-                    ts.factory.createCallExpression(
-                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('o'), 'set'),
-                        undefined,
-                        [
-                            ts.factory.createStringLiteral(jsonName),
-                            ts.factory.createCallExpression(
-                                ts.factory.createPropertyAccessExpression(
-                                    ts.factory.createPropertyAccessExpression(
-                                        ts.factory.createIdentifier('obj'),
-                                        fieldName
-                                    ),
-                                    'map'
-                                ),
-                                undefined,
-                                [
-                                    ts.factory.createArrowFunction(
-                                        undefined,
-                                        undefined,
-                                        [ts.factory.createParameterDeclaration(undefined, undefined, undefined, 'i')],
-                                        undefined,
-                                        ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
-                                        ts.factory.createCallExpression(
-                                            ts.factory.createPropertyAccessExpression(
-                                                ts.factory.createIdentifier(itemSerializer),
-                                                'toJson'
-                                            ),
-                                            undefined,
-                                            [ts.factory.createIdentifier('i')]
-                                        )
-                                    )
-                                ]
-                            )
-                        ]
-                    )
-                )
-            );
-        } else if (isMap(type.type)) {
-            const mapType = type.type as ts.TypeReference;
-            if (!isPrimitiveType(mapType.typeArguments![0])) {
-                throw new Error('only Map<Primitive, *> maps are supported extend if needed!');
-            }
-
-            let writeValue: ts.Expression;
-            if (isPrimitiveToJson(mapType.typeArguments![1], typeChecker)) {
-                writeValue = ts.factory.createIdentifier('v');
-            } else if (isEnumType(mapType.typeArguments![1])) {
-                writeValue = ts.factory.createAsExpression(
-                    ts.factory.createIdentifier('v'),
-                    ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
-                );
-            } else {
-                const itemSerializer = mapType.typeArguments![1].symbol.name + 'Serializer';
-                importer(itemSerializer, findSerializerModule(mapType.typeArguments![1], program.getCompilerOptions()));
-
-                writeValue = ts.factory.createCallExpression(
-                    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(itemSerializer), 'toJson'),
-                    undefined,
-                    [ts.factory.createIdentifier('v')]
-                );
-            }
-
-            propertyStatements.push(
-                ts.factory.createBlock([
-                    ts.factory.createVariableStatement(
-                        undefined,
-                        ts.factory.createVariableDeclarationList(
-                            [
-                                ts.factory.createVariableDeclaration(
-                                    'm',
-                                    undefined,
-                                    undefined,
-                                    ts.factory.createNewExpression(
-                                        ts.factory.createIdentifier('Map'),
-                                        [
-                                            ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
-                                            ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
-                                        ],
-                                        []
-                                    )
-                                )
-                            ],
-                            ts.NodeFlags.Const
-                        )
-                    ),
-                    ts.factory.createExpressionStatement(
-                        ts.factory.createCallExpression(
-                            ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('o'), 'set'),
-                            undefined,
-                            [ts.factory.createStringLiteral(jsonName), ts.factory.createIdentifier('m')]
-                        )
-                    ),
-
-                    ts.factory.createForOfStatement(
-                        undefined,
-                        ts.factory.createVariableDeclarationList(
-                            [
-                                ts.factory.createVariableDeclaration(
-                                    ts.factory.createArrayBindingPattern([
-                                        ts.factory.createBindingElement(undefined, undefined, 'k'),
-                                        ts.factory.createBindingElement(undefined, undefined, 'v')
-                                    ])
-                                )
-                            ],
-                            ts.NodeFlags.Const
-                        ),
-                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('obj'), fieldName),
-                        ts.factory.createBlock([
-                            ts.factory.createExpressionStatement(
-                                ts.factory.createCallExpression(
-                                    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('m'), 'set'),
-                                    undefined,
-                                    [
-                                        // todo: key to string
-                                        ts.factory.createCallExpression(
-                                            ts.factory.createPropertyAccessExpression(
-                                                ts.factory.createIdentifier('k'),
-                                                'toString'
-                                            ),
-                                            undefined,
-                                            []
-                                        ),
-                                        writeValue
-                                    ]
-                                )
-                            )
-                        ])
-                    )
-                ])
-            );
-        } else if (isImmutable(type.type)) {
-            let itemSerializer = type.type.symbol.name;
-            importer(itemSerializer, findModule(type.type, program.getCompilerOptions()));
-            propertyStatements.push(
-                ts.factory.createExpressionStatement(
-                    ts.factory.createCallExpression(
-                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('o'), 'set'),
-                        undefined,
-                        [
-                            ts.factory.createStringLiteral(jsonName),
-                            ts.factory.createCallExpression(
-                                ts.factory.createPropertyAccessExpression(
-                                    ts.factory.createIdentifier(itemSerializer),
-                                    'toJson'
-                                ),
-                                [],
-                                [
-                                    ts.factory.createPropertyAccessExpression(
-                                        ts.factory.createIdentifier('obj'),
-                                        fieldName
-                                    )
-                                ]
-                            )
-                        ]
-                    )
-                )
-            );
-        } else {
-            let itemSerializer = type.type.symbol.name + 'Serializer';
-            importer(itemSerializer, findSerializerModule(type.type, program.getCompilerOptions()));
-            propertyStatements.push(
-                ts.factory.createExpressionStatement(
-                    ts.factory.createCallExpression(
-                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('o'), 'set'),
-                        undefined,
-                        [
-                            ts.factory.createStringLiteral(jsonName),
-                            ts.factory.createCallExpression(
-                                ts.factory.createPropertyAccessExpression(
-                                    ts.factory.createIdentifier(itemSerializer),
-                                    'toJson'
-                                ),
-                                [],
-                                [
-                                    ts.factory.createPropertyAccessExpression(
-                                        ts.factory.createIdentifier('obj'),
-                                        fieldName
-                                    )
-                                ]
-                            )
-                        ]
-                    )
-                )
-            );
-        }
-
-        if (prop.target) {
-            propertyStatements = propertyStatements.map(s =>
-                ts.addSyntheticLeadingComment(s, ts.SyntaxKind.MultiLineCommentTrivia, `@target ${prop.target}`, true)
-            );
-        }
-
-        statements.push(...propertyStatements);
-    }
-
-    statements.push(ts.factory.createReturnStatement(ts.factory.createIdentifier('o')));
-
-    return ts.factory.createBlock(addNewLines(statements));
-}
-
-function createToJsonMethod(
-    program: ts.Program,
-    input: ts.ClassDeclaration,
-    propertiesToSerialize: JsonProperty[],
-    importer: (name: string, module: string) => void
-) {
-    return ts.factory.createMethodDeclaration(
-        undefined,
-        [
-            ts.factory.createModifier(ts.SyntaxKind.PublicKeyword),
-            ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)
-        ],
-        undefined,
-        'toJson',
-        undefined,
-        undefined,
-        [
-            ts.factory.createParameterDeclaration(
-                undefined,
-                undefined,
-                undefined,
-                'obj',
-                undefined,
-                ts.factory.createUnionTypeNode([
-                    ts.factory.createTypeReferenceNode(input.name!.text, undefined),
-                    ts.factory.createLiteralTypeNode(ts.factory.createNull())
-                ])
-            )
-        ],
-        ts.factory.createUnionTypeNode([
-            createStringUnknownMapNode(),
-            ts.factory.createLiteralTypeNode(ts.factory.createNull())
-        ]),
-        generateToJsonBody(program, propertiesToSerialize, importer)
-    );
-}
-
-//
-// setProperty
-
-function isPrimitiveFromJson(type: ts.Type, typeChecker: ts.TypeChecker) {
-    if (!type) {
-        return false;
-    }
-
-    const isArray = isTypedArray(type);
-    const arrayItemType = unwrapArrayItemType(type, typeChecker);
-
-    if (hasFlag(type, ts.TypeFlags.Unknown)) {
-        return true;
-    }
-    if (hasFlag(type, ts.TypeFlags.Number)) {
-        return true;
-    }
-    if (hasFlag(type, ts.TypeFlags.String)) {
-        return true;
-    }
-    if (hasFlag(type, ts.TypeFlags.Boolean)) {
-        return true;
-    }
-
-    if (arrayItemType) {
-        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Number)) {
-            return true;
-        }
-        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.String)) {
-            return true;
-        }
-        if (isArray && hasFlag(arrayItemType, ts.TypeFlags.Boolean)) {
-            return true;
-        }
-    } else if (type.symbol) {
-        switch (type.symbol.name) {
-            case 'Uint8Array':
-            case 'Uint16Array':
-            case 'Uint32Array':
-            case 'Int8Array':
-            case 'Int16Array':
-            case 'Int32Array':
-            case 'Float32Array':
-            case 'Float64Array':
-                return true;
-        }
-    }
-
-    return null;
-}
-
-function createEnumMapping(type: ts.Type): ts.Expression {
-    return ts.factory.createCallExpression(
-        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('JsonHelper'), 'parseEnum'),
-        [ts.factory.createTypeReferenceNode(type.symbol.name)],
-        [ts.factory.createIdentifier('v'), ts.factory.createIdentifier(type.symbol.name)]
-    );
-}
-
-function cloneTypeNode(node: ts.TypeNode): ts.TypeNode {
-    if(ts.isUnionTypeNode(node)) {
-        return ts.factory.createUnionTypeNode(node.types.map(cloneTypeNode));
-    } else if(node.kind === ts.SyntaxKind.StringKeyword 
-        || node.kind === ts.SyntaxKind.NumberKeyword
-        || node.kind === ts.SyntaxKind.BooleanKeyword
-        || node.kind === ts.SyntaxKind.UnknownKeyword
-        || node.kind === ts.SyntaxKind.AnyKeyword
-        || node.kind === ts.SyntaxKind.VoidKeyword) {
-        return ts.factory.createKeywordTypeNode(node.kind);
-    } else if(ts.isLiteralTypeNode(node)) {
-        return ts.factory.createLiteralTypeNode(node.literal);
-    } else if(ts.isArrayTypeNode(node)) {
-        return ts.factory.createArrayTypeNode(cloneTypeNode(node.elementType));
-    }
-
-    throw new Error(`Unsupported TypeNode: '${ts.SyntaxKind[node.kind]}' extend type node cloning`);
-}
-
-function generateSetPropertyBody(
-    program: ts.Program,
-    propertiesToSerialize: JsonProperty[],
-    importer: (name: string, module: string) => void
-) {
-    const statements: ts.Statement[] = [];
-    const cases: ts.CaseOrDefaultClause[] = [];
-
-    const typeChecker = program.getTypeChecker();
-    for (const prop of propertiesToSerialize) {
-        const jsonNames = prop.jsonNames.map(j => j.toLowerCase());
-        const caseValues: string[] = jsonNames.filter(j => j !== '');
-        const fieldName = (prop.property.name as ts.Identifier).text;
-
-        const caseStatements: ts.Statement[] = [];
-
-        const type = getTypeWithNullableInfo(typeChecker, prop.property.type);
-
-        const assignField = function (expr: ts.Expression): ts.Statement {
-            return ts.factory.createExpressionStatement(
-                ts.factory.createAssignment(
-                    ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('obj'), fieldName),
-                    expr
-                )
-            );
-        };
-
-        if (isPrimitiveFromJson(type.type!, typeChecker)) {
-            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 (isEnumType(type.type)) {
-            // obj.fieldName = enummapping
-            // return true;
-            importer(type.type.symbol!.name, findModule(type.type, program.getCompilerOptions()));
-            importer('JsonHelper', '@src/io/JsonHelper');
-            const read = createEnumMapping(type.type);
-            if (type.isNullable) {
-                caseStatements.push(assignField(read));
-            } else {
-                caseStatements.push(assignField(ts.factory.createNonNullExpression(read)));
-            }
-            caseStatements.push(ts.factory.createReturnStatement(ts.factory.createTrue()));
-        } else if (isTypedArray(type.type!)) {
-            const arrayItemType = unwrapArrayItemType(type.type!, typeChecker)!;
-            const collectionAddMethod = ts
-                .getJSDocTags(prop.property)
-                .filter(t => t.tagName.text === 'json_add')
-                .map(t => t.comment ?? '')[0] as string;
-
-            // obj.fieldName = [];
-            // for(const i of value) {
-            //    obj.addFieldName(Type.FromJson(i));
-            // }
-            // or
-            // for(const __li of value) {
-            //    obj.fieldName.push(Type.FromJson(__li));
-            // }
-
-            let itemSerializer = arrayItemType.symbol.name + 'Serializer';
-            importer(itemSerializer, findSerializerModule(arrayItemType, program.getCompilerOptions()));
-            importer(arrayItemType.symbol.name, findModule(arrayItemType, program.getCompilerOptions()));
-
-            const loopItems = [
-                assignField(ts.factory.createArrayLiteralExpression(undefined)),
-                ts.factory.createForOfStatement(
-                    undefined,
-                    ts.factory.createVariableDeclarationList(
-                        [ts.factory.createVariableDeclaration('o')],
-                        ts.NodeFlags.Const
-                    ),
-                    ts.factory.createAsExpression(
-                        ts.factory.createIdentifier('v'),
-                        ts.factory.createArrayTypeNode(
-                            ts.factory.createUnionTypeNode([
-                                createStringUnknownMapNode(),
-                                ts.factory.createLiteralTypeNode(ts.factory.createNull())
-                            ])
-                        )
-                    ),
-                    ts.factory.createBlock(
-                        [
-                            ts.factory.createVariableStatement(
-                                undefined,
-                                ts.factory.createVariableDeclarationList(
-                                    [
-                                        ts.factory.createVariableDeclaration(
-                                            'i',
-                                            undefined,
-                                            undefined,
-                                            ts.factory.createNewExpression(
-                                                ts.factory.createIdentifier(arrayItemType.symbol.name),
-                                                undefined,
-                                                []
-                                            )
-                                        )
-                                    ],
-                                    ts.NodeFlags.Const
-                                )
-                            ),
-                            ts.factory.createCallExpression(
-                                ts.factory.createPropertyAccessExpression(
-                                    ts.factory.createIdentifier(itemSerializer),
-                                    'fromJson'
-                                ),
-                                undefined,
-                                [ts.factory.createIdentifier('i'), ts.factory.createIdentifier('o')]
-                            ),
-                            ts.factory.createExpressionStatement(
-                                collectionAddMethod
-                                    ? // obj.addFieldName(i)
-                                      ts.factory.createCallExpression(
-                                          ts.factory.createPropertyAccessExpression(
-                                              ts.factory.createIdentifier('obj'),
-                                              collectionAddMethod
-                                          ),
-                                          undefined,
-                                          [ts.factory.createIdentifier('i')]
-                                      )
-                                    : // obj.fieldName.push(i)
-                                      ts.factory.createCallExpression(
-                                          ts.factory.createPropertyAccessExpression(
-                                              ts.factory.createPropertyAccessExpression(
-                                                  ts.factory.createIdentifier('obj'),
-                                                  fieldName
-                                              ),
-                                              'push'
-                                          ),
-                                          undefined,
-                                          [ts.factory.createIdentifier('i')]
-                                      )
-                            )
-                        ].filter(s => !!s) as ts.Statement[]
-                    )
-                )
-            ];
-
-            if (type.isNullable) {
-                caseStatements.push(
-                    ts.factory.createIfStatement(ts.factory.createIdentifier('v'), ts.factory.createBlock(loopItems))
-                );
-            } else {
-                caseStatements.push(...loopItems);
-            }
-            caseStatements.push(ts.factory.createReturnStatement(ts.factory.createTrue()));
-        } else if (isMap(type.type)) {
-            const mapType = type.type as ts.TypeReference;
-            if (!isPrimitiveType(mapType.typeArguments![0])) {
-                throw new Error('only Map<EnumType, *> maps are supported extend if needed!');
-            }
-
-            let mapKey;
-            if (isEnumType(mapType.typeArguments![0])) {
-                importer(
-                    mapType.typeArguments![0].symbol!.name,
-                    findModule(mapType.typeArguments![0], program.getCompilerOptions())
-                );
-                importer('JsonHelper', '@src/io/JsonHelper');
-                mapKey = ts.factory.createNonNullExpression(
-                    ts.factory.createCallExpression(
-                        ts.factory.createPropertyAccessExpression(
-                            ts.factory.createIdentifier('JsonHelper'),
-                            'parseEnum'
-                        ),
-                        [ts.factory.createTypeReferenceNode(mapType.typeArguments![0].symbol!.name)],
-                        [
-                            ts.factory.createIdentifier('k'),
-                            ts.factory.createIdentifier(mapType.typeArguments![0].symbol!.name)
-                        ]
-                    )
-                );
-            } else if (isNumberType(mapType.typeArguments![0])) {
-                mapKey = ts.factory.createCallExpression(ts.factory.createIdentifier('parseInt'), undefined, [
-                    ts.factory.createIdentifier('k')
-                ]);
-            } else {
-                mapKey = ts.factory.createIdentifier('k');
-            }
-
-            let mapValue;
-            let itemSerializer: string = '';
-            if (isPrimitiveFromJson(mapType.typeArguments![1], typeChecker)) {
-                // const isNullable = mapType.typeArguments![1].flags & ts.TypeFlags.Union
-                //     && !!(mapType.typeArguments![1] as ts.UnionType).types.find(t => t.flags & ts.TypeFlags.Null);
-
-                mapValue = ts.factory.createAsExpression(
-                    ts.factory.createIdentifier('v'),
-                    ts.isTypeReferenceNode(prop.property.type!) && prop.property.type.typeArguments
-                        ? cloneTypeNode(prop.property.type.typeArguments[1])
-                        : ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword)
-                );
-            } else {
-                itemSerializer = mapType.typeArguments![1].symbol.name + 'Serializer';
-                importer(itemSerializer, findSerializerModule(mapType.typeArguments![1], program.getCompilerOptions()));
-                importer(
-                    mapType.typeArguments![1]!.symbol.name,
-                    findModule(mapType.typeArguments![1], program.getCompilerOptions())
-                );
-                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;
-
-            caseStatements.push(
-                assignField(
-                    ts.factory.createNewExpression(
-                        ts.factory.createIdentifier('Map'),
-                        [
-                            typeChecker.typeToTypeNode(mapType.typeArguments![0], undefined, undefined)!,
-                            typeChecker.typeToTypeNode(mapType.typeArguments![1], undefined, undefined)!
-                        ],
-                        []
-                    )
-                )
-            );
-
-            caseStatements.push(
-                ts.factory.createExpressionStatement(
-                    ts.factory.createCallExpression(
-                        ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('JsonHelper'), 'forEach'),
-                        undefined,
-                        [
-                            ts.factory.createIdentifier('v'),
-                            ts.factory.createArrowFunction(
-                                undefined,
-                                undefined,
-                                [
-                                    ts.factory.createParameterDeclaration(undefined, undefined, undefined, 'v'),
-                                    ts.factory.createParameterDeclaration(undefined, undefined, undefined, 'k')
-                                ],
-                                undefined,
-                                ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
-                                ts.factory.createBlock(
-                                    addNewLines(
-                                        [
-                                            itemSerializer.length > 0 &&
-                                                ts.factory.createVariableStatement(
-                                                    undefined,
-                                                    ts.factory.createVariableDeclarationList(
-                                                        [
-                                                            ts.factory.createVariableDeclaration(
-                                                                'i',
-                                                                undefined,
-                                                                undefined,
-                                                                ts.factory.createNewExpression(
-                                                                    ts.factory.createIdentifier(
-                                                                        mapType.typeArguments![1].symbol.name
-                                                                    ),
-                                                                    undefined,
-                                                                    []
-                                                                )
-                                                            )
-                                                        ],
-                                                        ts.NodeFlags.Const
-                                                    )
-                                                ),
-                                            itemSerializer.length > 0 &&
-                                                ts.factory.createExpressionStatement(
-                                                    ts.factory.createCallExpression(
-                                                        ts.factory.createPropertyAccessExpression(
-                                                            ts.factory.createIdentifier(itemSerializer),
-                                                            'fromJson'
-                                                        ),
-                                                        undefined,
-                                                        [
-                                                            ts.factory.createIdentifier('i'),
-                                                            ts.factory.createAsExpression(
-                                                                ts.factory.createIdentifier('v'),
-                                                                createStringUnknownMapNode()
-                                                            )
-                                                        ]
-                                                    )
-                                                ),
-                                            ts.factory.createExpressionStatement(
-                                                ts.factory.createCallExpression(
-                                                    collectionAddMethod
-                                                        ? ts.factory.createPropertyAccessExpression(
-                                                              ts.factory.createIdentifier('obj'),
-                                                              collectionAddMethod
-                                                          )
-                                                        : ts.factory.createPropertyAccessExpression(
-                                                              ts.factory.createPropertyAccessExpression(
-                                                                  ts.factory.createIdentifier('obj'),
-                                                                  ts.factory.createIdentifier(fieldName)
-                                                              ),
-                                                              ts.factory.createIdentifier('set')
-                                                          ),
-                                                    undefined,
-                                                    [mapKey, mapValue]
-                                                )
-                                            )
-                                        ].filter(s => !!s) as ts.Statement[]
-                                    )
-                                )
-                            )
-                        ]
-                    )
-                )
-            );
-
-            caseStatements.push(ts.factory.createReturnStatement(ts.factory.createTrue()));
-        } else if (isImmutable(type.type)) {
-            let itemSerializer = type.type.symbol.name;
-            importer(itemSerializer, findModule(type.type, program.getCompilerOptions()));
-
-            // obj.fieldName = TypeName.fromJson(value)!
-            // return true;
-            caseStatements.push(
-                assignField(
-                    wrapToNonNull(
-                        type.isNullable,
-                        ts.factory.createCallExpression(
-                            // TypeName.fromJson
-                            ts.factory.createPropertyAccessExpression(
-                                ts.factory.createIdentifier(itemSerializer),
-                                'fromJson'
-                            ),
-                            [],
-                            [ts.factory.createIdentifier('v')]
-                        ),
-                        ts.factory
-                    )
-                )
-            );
-            caseStatements.push(ts.factory.createReturnStatement(ts.factory.createTrue()));
-        } else {
-            // for complex types it is a bit more tricky
-            // if the property matches exactly, we use fromJson
-            // if the property starts with the field name, we try to set a sub-property
-            const jsonNameArray = ts.factory.createArrayLiteralExpression(
-                jsonNames.map(n => ts.factory.createStringLiteral(n))
-            );
-
-            let itemSerializer = type.type.symbol.name + 'Serializer';
-            importer(itemSerializer, findSerializerModule(type.type, program.getCompilerOptions()));
-            if (type.isNullable) {
-                importer(type.type.symbol!.name, findModule(type.type, program.getCompilerOptions()));
-            }
-
-            // TODO if no partial name support, simply generate cases
-            statements.push(
-                ts.factory.createIfStatement(
-                    // if(["", "core"].indexOf(property) >= 0)
-                    ts.factory.createBinaryExpression(
-                        ts.factory.createCallExpression(
-                            ts.factory.createPropertyAccessExpression(jsonNameArray, 'indexOf'),
-                            [],
-                            [ts.factory.createIdentifier('property')]
-                        ),
-                        ts.SyntaxKind.GreaterThanEqualsToken,
-                        ts.factory.createNumericLiteral('0')
-                    ),
-                    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.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[]
-                                          )
-                                      )
-                                  ])
-                              )
-                          ])
-                )
-            );
-        }
-
-        if (caseStatements.length > 0) {
-            for (let i = 0; i < caseValues.length; i++) {
-                let caseClause = ts.factory.createCaseClause(
-                    ts.factory.createStringLiteral(caseValues[i]),
-                    // last case gets the statements, others are fall through
-                    i < caseValues.length - 1 ? [] : caseStatements
-                );
-                if (prop.target && i === 0) {
-                    caseClause = ts.addSyntheticLeadingComment(
-                        caseClause,
-                        ts.SyntaxKind.MultiLineCommentTrivia,
-                        `@target ${prop.target}`,
-                        true
-                    );
-                }
-                cases.push(caseClause);
-            }
-        }
-    }
-
-    if (cases.length > 0) {
-        const switchExpr = ts.factory.createSwitchStatement(
-            ts.factory.createIdentifier('property'),
-            ts.factory.createCaseBlock(cases)
-        );
-        statements.unshift(switchExpr);
-    }
-
-    statements.push(ts.factory.createReturnStatement(ts.factory.createFalse()));
-
-    return ts.factory.createBlock(addNewLines(statements));
-}
-
-function createSetPropertyMethod(
-    program: ts.Program,
-    input: ts.ClassDeclaration,
-    propertiesToSerialize: JsonProperty[],
-    importer: (name: string, module: string) => void
-) {
-    return ts.factory.createMethodDeclaration(
-        undefined,
-        [
-            ts.factory.createModifier(ts.SyntaxKind.PublicKeyword),
-            ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)
-        ],
-        undefined,
-        'setProperty',
-        undefined,
-        undefined,
-        [
-            ts.factory.createParameterDeclaration(
-                undefined,
-                undefined,
-                undefined,
-                'obj',
-                undefined,
-                ts.factory.createTypeReferenceNode(input.name!.text, undefined)
-            ),
-            ts.factory.createParameterDeclaration(
-                undefined,
-                undefined,
-                undefined,
-                'property',
-                undefined,
-                ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
-            ),
-            ts.factory.createParameterDeclaration(
-                undefined,
-                undefined,
-                undefined,
-                'v',
-                undefined,
-                ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
-            )
-        ],
-        ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
-        generateSetPropertyBody(program, propertiesToSerialize, importer)
-    );
-}
 
 export default createEmitter('json', (program, input) => {
     console.log(`Writing Serializer for ${input.name!.text}`);
@@ -1227,7 +19,13 @@ export default createEmitter('json', (program, input) => {
         path.resolve(input.getSourceFile().fileName)
     );
 
-    let propertiesToSerialize: JsonProperty[] = [];
+    const serializable: JsonSerializable = {
+        properties: [],
+        isStrict: !!ts.getJSDocTags(input).find(t => t.tagName.text === 'json_strict'),
+        hasToJsonExtension: false,
+        hasSetPropertyExtension: false
+    };
+
     input.members.forEach(member => {
         if (ts.isPropertyDeclaration(member)) {
             const propertyDeclaration = member as ts.PropertyDeclaration;
@@ -1236,14 +34,14 @@ export default createEmitter('json', (program, input) => {
                     m => m.kind === ts.SyntaxKind.StaticKeyword || m.kind === ts.SyntaxKind.PrivateKeyword
                 )
             ) {
-                const jsonNames = [(member.name as ts.Identifier).text];
+                const jsonNames = [(member.name as ts.Identifier).text.toLowerCase()];
 
                 if (ts.getJSDocTags(member).find(t => t.tagName.text === 'json_on_parent')) {
                     jsonNames.push('');
                 }
 
                 if (!ts.getJSDocTags(member).find(t => t.tagName.text === 'json_ignore')) {
-                    propertiesToSerialize.push({
+                    serializable.properties.push({
                         property: propertyDeclaration,
                         jsonNames: jsonNames,
                         partialNames: !!ts.getJSDocTags(member).find(t => t.tagName.text === 'json_partial_names'),
@@ -1252,6 +50,16 @@ export default createEmitter('json', (program, input) => {
                 }
             }
         }
+        else if (ts.isMethodDeclaration(member)) {
+            switch ((member.name as ts.Identifier).text) {
+                case 'toJson':
+                    serializable.hasToJsonExtension = true;
+                    break;
+                case 'setProperty':
+                    serializable.hasSetPropertyExtension = true;
+                    break;
+            }
+        }
     });
 
     const statements: ts.Statement[] = [];
@@ -1286,9 +94,9 @@ export default createEmitter('json', (program, input) => {
             undefined,
             undefined,
             [
-                createFromJsonMethod(input, importer),
-                createToJsonMethod(program, input, propertiesToSerialize, importer),
-                createSetPropertyMethod(program, input, propertiesToSerialize, importer)
+                createFromJsonMethod(input, serializable, importer),
+                createToJsonMethod(program, input, serializable, importer),
+                createSetPropertyMethod(program, input, serializable, importer)
             ]
         )
     );
diff --git a/src.csharp/AlphaTab.Test/Model/ComparisonHelpers.cs b/src.csharp/AlphaTab.Test/Model/ComparisonHelpers.cs
new file mode 100644
index 000000000..b27a4fd39
--- /dev/null
+++ b/src.csharp/AlphaTab.Test/Model/ComparisonHelpers.cs
@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+using AlphaTab.Collections;
+using AlphaTab.Test;
+
+namespace AlphaTab.Model
+{
+    internal partial class ComparisonHelpers
+    {
+        private static bool CompareObjects(object? expected, object? actual, string path,
+            IList<string> ignoreKeys)
+        {
+            Globals.Fail(
+                $"cannot compare unknown object types expected[{actual?.GetType().FullName}] expected[${expected?.GetType().FullName}]");
+            return false;
+        }
+    }
+}
diff --git a/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs b/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs
index 03ee71c3a..2e97f59a1 100644
--- a/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs
+++ b/src.csharp/AlphaTab.Test/VisualTests/VisualTestHelper.cs
@@ -61,7 +61,7 @@ public static async Task RunVisualTestScore(Score score, string referenceFileNam
             IList<double>? tracks = null, string? message = null, double tolerancePercent = 1, bool triggerResize = false)
         {
             settings ??= new Settings();
-            tracks ??= new Core.List<double> {0};
+            tracks ??= new AlphaTab.Collections.List<double> {0};
 
             settings.Core.Engine = "skia";
             settings.Core.EnableLazyLoading = false;
@@ -89,7 +89,7 @@ public static async Task RunVisualTestScore(Score score, string referenceFileNam
             var referenceFileData =
                 await TestPlatform.LoadFile(referenceFileName);
 
-            var result = new AlphaTab.Core.List<RenderFinishedEventArgs>();
+            var result = new AlphaTab.Collections.List<RenderFinishedEventArgs>();
             var totalWidth = 0.0;
             var totalHeight = 0.0;
             var isResizeRender = false;
@@ -101,7 +101,7 @@ public static async Task RunVisualTestScore(Score score, string referenceFileNam
             };
             renderer.PreRender.On(isResize =>
             {
-                result = new AlphaTab.Core.List<RenderFinishedEventArgs>();
+                result = new AlphaTab.Collections.List<RenderFinishedEventArgs>();
                 totalWidth = 0.0;
                 totalHeight = 0.0;
             });
@@ -177,7 +177,7 @@ private static void LoadFonts()
         }
 
         private static void CompareVisualResult(double totalWidth, double totalHeight,
-            AlphaTab.Core.List<RenderFinishedEventArgs> result, string referenceFileName,
+            AlphaTab.Collections.List<RenderFinishedEventArgs> result, string referenceFileName,
             Uint8Array referenceFileData, string? message, double tolerancePercent = 1)
         {
             SKBitmap finalBitmap;
diff --git a/src.csharp/AlphaTab/Core/List.cs b/src.csharp/AlphaTab/Collections/List.cs
similarity index 91%
rename from src.csharp/AlphaTab/Core/List.cs
rename to src.csharp/AlphaTab/Collections/List.cs
index 7c4c6ff21..d176b2990 100644
--- a/src.csharp/AlphaTab/Core/List.cs
+++ b/src.csharp/AlphaTab/Collections/List.cs
@@ -1,6 +1,6 @@
 using System.Collections.Generic;
 
-namespace AlphaTab.Core
+namespace AlphaTab.Collections
 {
     internal class List<T> : System.Collections.Generic.List<T>
     {
diff --git a/src.csharp/AlphaTab/Collections/Map.cs b/src.csharp/AlphaTab/Collections/Map.cs
new file mode 100644
index 000000000..7e8525881
--- /dev/null
+++ b/src.csharp/AlphaTab/Collections/Map.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AlphaTab.Collections
+{
+    public interface IMap
+    {
+        double Size { get; }
+        void Clear();
+    }
+    public interface IMap<TKey, TValue> : IMap, IEnumerable<MapEntry<TKey, TValue>>
+        where TValue : class?
+    {
+        IEnumerable<TKey> Keys();
+        IEnumerable<TValue> Values();
+        bool Has(TKey key);
+        TValue Get(TKey key);
+        void Set(TKey key, TValue value);
+        void Delete(TKey key);
+    }
+
+    public class Map<TKey, TValue> : Dictionary<TKey, TValue>, IMap<TKey, TValue>
+        where TValue : class?
+    {
+        public double Size => Count;
+        IEnumerable<TKey> IMap<TKey, TValue>.Keys()
+        {
+            return base.Keys;
+        }
+
+        IEnumerable<TValue> IMap<TKey, TValue>.Values()
+        {
+            return base.Values;
+        }
+
+        public Map()
+        {
+        }
+
+        public Map(IEnumerable<MapEntry<TKey, TValue>> entries)
+        {
+            foreach (var entry in entries)
+            {
+                this[entry.Key] = entry.Value;
+            }
+        }
+        public Map(IEnumerable<KeyValuePair<TKey, TValue>> entries)
+        {
+            foreach (var entry in entries)
+            {
+                this[entry.Key] = entry.Value;
+            }
+        }
+
+        public bool Has(TKey key)
+        {
+            return ContainsKey(key);
+        }
+
+        public TValue Get(TKey key)
+        {
+            return this[key];
+        }
+
+        public void Set(TKey key, TValue value)
+        {
+            this[key] = value;
+        }
+
+        IEnumerator<MapEntry<TKey, TValue>> IEnumerable<MapEntry<TKey, TValue>>.GetEnumerator()
+        {
+            return ((IEnumerable<KeyValuePair<TKey, TValue>>) this).Select(kvp =>
+                new MapEntry<TKey, TValue>(kvp)).GetEnumerator();
+        }
+
+        public void Delete(TKey key)
+        {
+            Remove(key);
+        }
+    }
+}
diff --git a/src.csharp/AlphaTab/Core/EcmaScript/MapEntry.cs b/src.csharp/AlphaTab/Collections/MapEntry.cs
similarity index 58%
rename from src.csharp/AlphaTab/Core/EcmaScript/MapEntry.cs
rename to src.csharp/AlphaTab/Collections/MapEntry.cs
index b8fdb0719..b20b5ac4f 100644
--- a/src.csharp/AlphaTab/Core/EcmaScript/MapEntry.cs
+++ b/src.csharp/AlphaTab/Collections/MapEntry.cs
@@ -1,6 +1,8 @@
-namespace AlphaTab.Core.EcmaScript
+using System.Collections.Generic;
+
+namespace AlphaTab.Collections
 {
-    public class MapEntry<TKey, TValue>
+    public struct MapEntry<TKey, TValue>
     {
         public TKey Key { get; set; }
         public TValue Value { get; set; }
@@ -11,6 +13,12 @@ public MapEntry(TKey key, TValue value)
             Value = value;
         }
 
+        public MapEntry(KeyValuePair<TKey, TValue> kvp)
+        {
+            Key = kvp.Key;
+            Value = kvp.Value;
+        }
+
         public void Deconstruct(out TKey key, out TValue value)
         {
             key = Key;
diff --git a/src.csharp/AlphaTab/Collections/ValueTypeMap.cs b/src.csharp/AlphaTab/Collections/ValueTypeMap.cs
new file mode 100644
index 000000000..167ef74af
--- /dev/null
+++ b/src.csharp/AlphaTab/Collections/ValueTypeMap.cs
@@ -0,0 +1,84 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AlphaTab.Collections
+{
+    public interface IValueTypeMap<TKey, TValue> : IEnumerable<MapEntry<TKey, TValue>>
+        where TValue : struct
+    {
+        double Size { get; }
+        IEnumerable<TKey> Keys();
+        IEnumerable<TValue> Values();
+        bool Has(TKey key);
+        TValue? Get(TKey key);
+        void Set(TKey key, TValue value);
+        void Delete(TKey key);
+        void Clear();
+    }
+
+    public class ValueTypeMap<TKey, TValue> : Dictionary<TKey, TValue>, IValueTypeMap<TKey, TValue>
+        where TValue : struct
+    {
+        public double Size => Count;
+        IEnumerable<TKey> IValueTypeMap<TKey, TValue>.Keys()
+        {
+            return base.Keys;
+        }
+
+        IEnumerable<TValue> IValueTypeMap<TKey, TValue>.Values()
+        {
+            return base.Values;
+        }
+
+        public ValueTypeMap()
+        {
+        }
+
+        public ValueTypeMap(IEnumerable<MapEntry<TKey, TValue>> entries)
+        {
+            foreach (var entry in entries)
+            {
+                this[entry.Key] = entry.Value;
+            }
+        }
+
+        public ValueTypeMap(IEnumerable<KeyValuePair<TKey, TValue>> entries)
+        {
+            foreach (var entry in entries)
+            {
+                this[entry.Key] = entry.Value;
+            }
+        }
+
+        public bool Has(TKey key)
+        {
+            return ContainsKey(key);
+        }
+
+        public TValue? Get(TKey key)
+        {
+            if (TryGetValue(key, out var value))
+            {
+                return value;
+            }
+
+            return null;
+        }
+
+        public void Set(TKey key, TValue value)
+        {
+            this[key] = value;
+        }
+
+        public void Delete(TKey key)
+        {
+            Remove(key);
+        }
+
+        IEnumerator<MapEntry<TKey, TValue>> IEnumerable<MapEntry<TKey, TValue>>.GetEnumerator()
+        {
+            return ((IEnumerable<KeyValuePair<TKey, TValue>>) this).Select(kvp =>
+                new MapEntry<TKey, TValue>(kvp)).GetEnumerator();
+        }
+    }
+}
diff --git a/src.csharp/AlphaTab/Core/EcmaScript/Map.cs b/src.csharp/AlphaTab/Core/EcmaScript/Map.cs
deleted file mode 100644
index fe404c4ec..000000000
--- a/src.csharp/AlphaTab/Core/EcmaScript/Map.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace AlphaTab.Core.EcmaScript
-{
-    public abstract class Map
-    {
-    }
-
-    public class Map<TKey, TValue> : Map, IEnumerable<MapEntry<TKey, TValue>>,
-        IDictionary<TKey, TValue>
-        where TValue : class?
-    {
-        private readonly Dictionary<TKey, TValue> _data;
-
-        public Map()
-        {
-            _data = new Dictionary<TKey, TValue>();
-        }
-
-        public Map(IEnumerable<MapEntry<TKey, TValue>> entries)
-        {
-            _data = entries.ToDictionary(e => e.Key, e => e.Value);
-        }
-
-        public double Size => _data.Count;
-
-        public TValue Get(TKey key)
-        {
-            if (_data.TryGetValue(key, out var value))
-            {
-                return value;
-            }
-
-#pragma warning disable 8653
-            return default;
-#pragma warning restore 8653
-        }
-
-        public void Set(TKey key, TValue value)
-        {
-            _data[key] = value;
-        }
-
-        public bool Has(TKey key)
-        {
-            return _data.ContainsKey(key);
-        }
-
-        public void Delete(TKey key)
-        {
-            _data.Remove(key);
-        }
-
-        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
-        {
-            ((ICollection<KeyValuePair<TKey, TValue>>) _data).Add(item);
-        }
-
-        public void Clear()
-        {
-            _data.Clear();
-        }
-
-        bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
-        {
-            return ((ICollection<KeyValuePair<TKey, TValue>>) _data).Contains(item);
-        }
-
-        void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array,
-            int arrayIndex)
-        {
-            ((ICollection<KeyValuePair<TKey, TValue>>) _data).CopyTo(array, arrayIndex);
-        }
-
-        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
-        {
-            return ((ICollection<KeyValuePair<TKey, TValue>>) _data).Remove(item);
-        }
-
-        int ICollection<KeyValuePair<TKey, TValue>>.Count =>
-            ((ICollection<KeyValuePair<TKey, TValue>>) _data).Count;
-
-        bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly =>
-            ((ICollection<KeyValuePair<TKey, TValue>>) _data).IsReadOnly;
-
-        IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.
-            GetEnumerator()
-        {
-            return ((IEnumerable<KeyValuePair<TKey, TValue>>) _data).GetEnumerator();
-        }
-
-        public IEnumerator<MapEntry<TKey, TValue>> GetEnumerator()
-        {
-            return _data.Select(d => new MapEntry<TKey, TValue>(d.Key, d.Value)).GetEnumerator();
-        }
-
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return GetEnumerator();
-        }
-
-        public void ForEach(Action<TValue> callback)
-        {
-            foreach (var kvp in _data)
-            {
-                callback(kvp.Value);
-            }
-        }
-
-        public void ForEach(Action<TValue, TKey> callback)
-        {
-            foreach (var kvp in _data)
-            {
-                callback(kvp.Value, kvp.Key);
-            }
-        }
-
-        public IEnumerable<TKey> Keys()
-        {
-            return _data.Keys;
-        }
-
-        void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
-        {
-            _data.Add(key, value);
-        }
-
-        bool IDictionary<TKey, TValue>.ContainsKey(TKey key)
-        {
-            return _data.ContainsKey(key);
-        }
-
-        bool IDictionary<TKey, TValue>.Remove(TKey key)
-        {
-            return _data.Remove(key);
-        }
-
-        bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value)
-        {
-            return _data.TryGetValue(key, out value);
-        }
-
-        TValue IDictionary<TKey, TValue>.this[TKey key]
-        {
-            get => _data[key];
-            set => _data[key] = value;
-        }
-
-        ICollection<TKey> IDictionary<TKey, TValue>.Keys => _data.Keys;
-
-        ICollection<TValue> IDictionary<TKey, TValue>.Values => _data.Values;
-
-        public IEnumerable<TValue> Values()
-        {
-            return _data.Values;
-        }
-    }
-}
diff --git a/src.csharp/AlphaTab/Core/EcmaScript/ValueTypeMap.cs b/src.csharp/AlphaTab/Core/EcmaScript/ValueTypeMap.cs
deleted file mode 100644
index 1f7a8f0b3..000000000
--- a/src.csharp/AlphaTab/Core/EcmaScript/ValueTypeMap.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace AlphaTab.Core.EcmaScript
-{
-    public class ValueTypeMap<TKey, TValue> : IEnumerable<MapEntry<TKey, TValue>>
-        where TValue : struct
-    {
-        private readonly Dictionary<TKey, TValue> _data;
-
-        public ValueTypeMap()
-        {
-            _data = new Dictionary<TKey, TValue>();
-        }
-
-        public ValueTypeMap(IEnumerable<MapEntry<TKey, TValue>> entries)
-        {
-            _data = entries.ToDictionary(e => e.Key, e => e.Value);
-        }
-
-        public double Size => _data.Count;
-
-        public TValue? Get(TKey key)
-        {
-            if (_data.TryGetValue(key, out var value))
-            {
-                return value;
-            }
-
-            return null;
-        }
-
-        public void Set(TKey key, TValue value)
-        {
-            _data[key] = value;
-        }
-
-        public bool Has(TKey key)
-        {
-            return _data.ContainsKey(key);
-        }
-
-        public void Delete(TKey key)
-        {
-            _data.Remove(key);
-        }
-
-        public void Clear()
-        {
-            _data.Clear();
-        }
-
-        public IEnumerator<MapEntry<TKey, TValue>> GetEnumerator()
-        {
-            return _data.Select(d => new MapEntry<TKey, TValue>(d.Key, d.Value)).GetEnumerator();
-        }
-
-        IEnumerator IEnumerable.GetEnumerator()
-        {
-            return GetEnumerator();
-        }
-
-        public void ForEach(Action<TValue, TKey> callback)
-        {
-            foreach (var kvp in _data)
-            {
-                callback(kvp.Value, kvp.Key);
-            }
-        }
-    }
-}
diff --git a/src.csharp/AlphaTab/Core/TypeHelper.cs b/src.csharp/AlphaTab/Core/TypeHelper.cs
index 9bcd973ae..7d5666617 100644
--- a/src.csharp/AlphaTab/Core/TypeHelper.cs
+++ b/src.csharp/AlphaTab/Core/TypeHelper.cs
@@ -33,7 +33,7 @@ public static IList<T> Splice<T>(this IList<T> data, double start, double delete
 
         public static IList<T> Slice<T>(this IList<T> data)
         {
-            return new AlphaTab.Core.List<T>(data);
+            return new AlphaTab.Collections.List<T>(data);
         }
 
         public static void Reverse<T>(this IList<T> data)
@@ -251,22 +251,22 @@ public static IList<string> Split(this string s, string separator)
             return new List<string>(s.Split(new[] {separator}, StringSplitOptions.None));
         }
 
-        public static MapEntry<double, TValue> CreateMapEntry<TValue>(int key, TValue value)
+        public static KeyValuePair<double, TValue> CreateMapEntry<TValue>(int key, TValue value)
         {
-            return new MapEntry<double, TValue>(key, value);
+            return new KeyValuePair<double, TValue>(key, value);
         }
 
-        public static MapEntry<TKey, double> CreateMapEntry<TKey>(TKey key, int value)
+        public static KeyValuePair<TKey, double> CreateMapEntry<TKey>(TKey key, int value)
         {
-            return new MapEntry<TKey, double>(key, value);
+            return new KeyValuePair<TKey, double>(key, value);
         }
 
-        public static MapEntry<TKey, TValue> CreateMapEntry<TKey, TValue>(TKey key, TValue value)
+        public static KeyValuePair<TKey, TValue> CreateMapEntry<TKey, TValue>(TKey key, TValue value)
         {
-            return new MapEntry<TKey, TValue>(key, value);
+            return new KeyValuePair<TKey, TValue>(key, value);
         }
 
-        public static string ToString(this double num, int radix)
+        public static string ToInvariantString(this double num, int radix)
         {
             if (radix == 16)
             {
@@ -276,6 +276,21 @@ public static string ToString(this double num, int radix)
             return num.ToString(CultureInfo.InvariantCulture);
         }
 
+        public static string ToInvariantString(this double num)
+        {
+            return num.ToString(CultureInfo.InvariantCulture);
+        }
+
+        public static string ToInvariantString(this int num)
+        {
+            return num.ToString(CultureInfo.InvariantCulture);
+        }
+
+        public static string ToInvariantString(this Enum num)
+        {
+            return ((IConvertible)num).ToInt32(CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture);
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static RegExp CreateRegex(string pattern, string flags)
         {
@@ -345,5 +360,11 @@ public static string TypeOf(object? actual)
                     return "object";
             }
         }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static IEnumerable<T> SetInitializer<T>(params T[] items)
+        {
+            return items;
+        }
     }
 }
diff --git a/src.csharp/AlphaTab/Environment.cs b/src.csharp/AlphaTab/Environment.cs
index bf8454f65..9f3b5f9bf 100644
--- a/src.csharp/AlphaTab/Environment.cs
+++ b/src.csharp/AlphaTab/Environment.cs
@@ -1,8 +1,10 @@
 
 using System;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
-using AlphaTab.Core.EcmaScript;
+using AlphaTab.Core;
+using AlphaTab.Collections;
 using AlphaTab.Platform.CSharp;
 
 namespace AlphaTab
@@ -12,7 +14,7 @@ partial class Environment
         public const bool SupportsTextDecoder = true;
         public static void PlatformInit()
         {
-            
+
         }
 
 
@@ -33,7 +35,7 @@ public static Action Throttle(Action action, double delay)
             };
         }
 
-        private static void CreatePlatformSpecificRenderEngines(Map<string, RenderEngineFactory> renderEngines)
+        private static void CreatePlatformSpecificRenderEngines(IMap<string, RenderEngineFactory> renderEngines)
         {
             renderEngines.Set(
                 "skia",
diff --git a/src.csharp/AlphaTab/Io/TypeConversions.cs b/src.csharp/AlphaTab/Io/TypeConversions.cs
new file mode 100644
index 000000000..59cd4e082
--- /dev/null
+++ b/src.csharp/AlphaTab/Io/TypeConversions.cs
@@ -0,0 +1,30 @@
+namespace AlphaTab.Io
+{
+    internal static class TypeConversions
+    {
+        public static double Int32ToUint16(double v)
+        {
+            return (ushort)(int)v;
+        }
+
+        public static double Int32ToInt16(double v)
+        {
+            return (short)(int)v;
+        }
+
+        public static double Int32ToUint32(double v)
+        {
+            return (uint)(int)v;
+        }
+
+        public static double Uint16ToInt16(double v)
+        {
+            return (short)(ushort)(int)v;
+        }
+
+        public static double Int16ToUint32(double v)
+        {
+            return (uint)(short)(int)v;
+        }
+    }
+}
diff --git a/src.csharp/AlphaTab/Platform/CSharp/ManagedThreadScoreRenderer.cs b/src.csharp/AlphaTab/Platform/CSharp/ManagedThreadScoreRenderer.cs
index 2d22f178a..d89019bfb 100644
--- a/src.csharp/AlphaTab/Platform/CSharp/ManagedThreadScoreRenderer.cs
+++ b/src.csharp/AlphaTab/Platform/CSharp/ManagedThreadScoreRenderer.cs
@@ -155,7 +155,7 @@ public void ResizeRender()
             }
         }
 
-        public void RenderScore(Score score, IList<double> trackIndexes)
+        public void RenderScore(Score? score, IList<double>? trackIndexes)
         {
             if (CheckAccess())
             {
diff --git a/src.csharp/AlphaTab/Platform/Svg/FontSizes.cs b/src.csharp/AlphaTab/Platform/Svg/FontSizes.cs
index 1963d453c..f14645d3f 100644
--- a/src.csharp/AlphaTab/Platform/Svg/FontSizes.cs
+++ b/src.csharp/AlphaTab/Platform/Svg/FontSizes.cs
@@ -1,4 +1,5 @@
-using AlphaTab.Core.EcmaScript;
+using AlphaTab.Core;
+using AlphaTab.Core.EcmaScript;
 
 namespace AlphaTab.Platform.Svg
 {
diff --git a/src.csharp/AlphaTab/Core/Lazy.cs b/src.csharp/AlphaTab/Util/Lazy.cs
similarity index 95%
rename from src.csharp/AlphaTab/Core/Lazy.cs
rename to src.csharp/AlphaTab/Util/Lazy.cs
index c412dc86f..3a1bfff78 100644
--- a/src.csharp/AlphaTab/Core/Lazy.cs
+++ b/src.csharp/AlphaTab/Util/Lazy.cs
@@ -1,4 +1,4 @@
-namespace AlphaTab.Core
+namespace AlphaTab.Util
 {
     public class Lazy<T> where T:class?
     {
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/EnvironmentPartials.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/EnvironmentPartials.kt
index 32ab5b5c9..5bbe0a094 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/EnvironmentPartials.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/EnvironmentPartials.kt
@@ -6,14 +6,14 @@ import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
 @ExperimentalUnsignedTypes
-internal expect fun createPlatformSpecificRenderEngines(engines: alphaTab.core.ecmaScript.Map<String, RenderEngineFactory>)
+internal expect fun createPlatformSpecificRenderEngines(engines: alphaTab.collections.Map<String, RenderEngineFactory>)
 
 @Suppress("UNUSED_PARAMETER")
 @kotlin.contracts.ExperimentalContracts
 @ExperimentalUnsignedTypes
 class EnvironmentPartials {
     companion object {
-        internal fun createPlatformSpecificRenderEngines(engines: alphaTab.core.ecmaScript.Map<String, RenderEngineFactory>) {
+        internal fun createPlatformSpecificRenderEngines(engines: alphaTab.collections.Map<String, RenderEngineFactory>) {
             alphaTab.createPlatformSpecificRenderEngines(engines)
         }
 
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/BooleanList.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/BooleanList.kt
new file mode 100644
index 000000000..c11d3efeb
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/BooleanList.kt
@@ -0,0 +1,20 @@
+package alphaTab.collections
+
+public interface IBooleanIterable : Iterable<Boolean> {
+    public override fun iterator(): BooleanIterator
+}
+
+internal class BooleanList(vararg elements: Boolean) : IBooleanIterable {
+    private val _data: BooleanArray = elements
+
+    public val length: Double
+        get() = _data.size.toDouble()
+
+    public operator fun get(index: Int): Boolean {
+        return _data[index]
+    }
+
+    public override fun iterator(): BooleanIterator {
+        return _data.iterator()
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleBooleanMap.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleBooleanMap.kt
new file mode 100644
index 000000000..086cb3ffd
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleBooleanMap.kt
@@ -0,0 +1,120 @@
+package alphaTab.collections
+
+internal open class DoubleBooleanMapEntry {
+    private var _key: Double = 0.0
+    public var key: Double
+        get() = _key
+        internal set(value) {
+            _key = value
+        }
+
+    private var _value: Boolean = false
+    public var value: Boolean
+        get() = _value
+        internal set(value) {
+            _value = value
+        }
+}
+
+internal class DoubleBooleanMapEntryInternal : DoubleBooleanMapEntry(), IMapEntryInternal {
+    public override var hashCode: Int = 0
+    public override var next: Int = 0
+
+    override fun reset() {
+        key = 0.0
+        value = false
+    }
+}
+
+internal class DoubleBooleanMap : MapBase<DoubleBooleanMapEntry, DoubleBooleanMapEntryInternal>() {
+    public fun has(key: Double): Boolean {
+        return findEntryInternal(key,
+            { entry, k -> entry.key == k }) >= 0
+    }
+
+    public fun get(key: Double): Boolean {
+        val i = findEntryInternal(key,
+            { entry, k -> entry.key == k })
+        if (i >= 0) {
+            return entries[i].value
+        }
+        throw KeyNotFoundException()
+    }
+
+    public fun set(key: Double, value: Boolean) {
+        insert(key, value)
+    }
+
+    private fun insert(key: Double, value: Boolean) {
+        insertInternal(key, value,
+            { entry, k -> entry.key = k },
+            { entry, v -> entry.value = v },
+            { entry, k -> entry.key == k }
+        )
+    }
+
+    public fun delete(key: Double) {
+        deleteInternal(key.hashCode())
+    }
+
+    private var _values: ValueCollection? = null
+    public fun values(): IBooleanIterable {
+        _values = _values ?: ValueCollection(this)
+        return _values!!
+    }
+
+    private var _keys: KeyCollection? = null
+    public fun keys(): IDoubleIterable {
+        _keys = _keys ?: KeyCollection(this)
+        return _keys!!
+    }
+
+    override fun createEntries(size: Int): Array<DoubleBooleanMapEntryInternal> {
+        return Array(size) {
+            DoubleBooleanMapEntryInternal()
+        }
+    }
+
+    override fun createEntries(
+        size: Int,
+        old: Array<DoubleBooleanMapEntryInternal>
+    ): Array<DoubleBooleanMapEntryInternal> {
+        return Array(size) {
+            if (it < old.size) old[it] else DoubleBooleanMapEntryInternal()
+        }
+    }
+
+    private class ValueCollection(private val map: DoubleBooleanMap) : IBooleanIterable {
+        override fun iterator(): BooleanIterator {
+            return ValueIterator(map.iterator())
+        }
+
+        private class ValueIterator(private val iterator: Iterator<DoubleBooleanMapEntry>) :
+            BooleanIterator() {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun nextBoolean(): Boolean {
+                return iterator.next().value
+            }
+        }
+    }
+
+    private class KeyCollection(private val map: DoubleBooleanMap) : IDoubleIterable {
+        override fun iterator(): DoubleIterator {
+            return KeyIterator(map.iterator())
+        }
+
+        private class KeyIterator(private val iterator: Iterator<DoubleBooleanMapEntry>) :
+            DoubleIterator() {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun nextDouble(): Double {
+                return iterator.next().key
+            }
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleDoubleMap.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleDoubleMap.kt
new file mode 100644
index 000000000..5f231b134
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleDoubleMap.kt
@@ -0,0 +1,121 @@
+package alphaTab.collections
+
+public open class DoubleDoubleMapEntry {
+    private var _key: Double = 0.0
+    public var key: Double
+        get() = _key
+        internal set(value) {
+            _key = value
+        }
+
+    private var _value: Double = 0.0
+    public var value: Double
+        get() = _value
+        internal set(value) {
+            _value = value
+        }
+}
+
+public class DoubleDoubleMapEntryInternal : DoubleDoubleMapEntry(), IMapEntryInternal {
+    public override var hashCode: Int = 0
+    public override var next: Int = 0
+
+    override fun reset() {
+        key = 0.0
+        value = 0.0
+    }
+}
+
+public class DoubleDoubleMap : MapBase<DoubleDoubleMapEntry, DoubleDoubleMapEntryInternal>() {
+    public fun has(key: Double): Boolean {
+        return findEntryInternal(key,
+            { entry, k -> entry.key == k }) >= 0
+    }
+
+    public fun get(key: Double): Double {
+        val i = findEntryInternal(key,
+            { entry, k -> entry.key == k })
+        if (i >= 0) {
+            return entries[i].value
+        }
+        throw KeyNotFoundException()
+    }
+
+    public fun set(key: Double, value: Double) {
+        insert(key, value)
+    }
+
+    private fun insert(key: Double, value: Double) {
+        insertInternal(key, value,
+            { entry, k -> entry.key = k },
+            { entry, v -> entry.value = v },
+            { entry, k -> entry.key == k }
+        )
+    }
+
+    public fun delete(key: Double) {
+        deleteInternal(key.hashCode())
+    }
+
+    private var _values: ValueCollection? = null
+    public fun values(): IDoubleIterable {
+        _values = _values ?: ValueCollection(this)
+        return _values!!
+    }
+
+    private var _keys: KeyCollection? = null
+    public fun keys(): IDoubleIterable {
+        _keys = _keys ?: KeyCollection(this)
+        return _keys!!
+    }
+
+
+    override fun createEntries(size: Int): Array<DoubleDoubleMapEntryInternal> {
+        return Array(size) {
+            DoubleDoubleMapEntryInternal()
+        }
+    }
+
+    override fun createEntries(
+        size: Int,
+        old: Array<DoubleDoubleMapEntryInternal>
+    ): Array<DoubleDoubleMapEntryInternal> {
+        return Array(size) {
+            if (it < old.size) old[it] else DoubleDoubleMapEntryInternal()
+        }
+    }
+
+    private class ValueCollection(private val map: DoubleDoubleMap) : IDoubleIterable {
+        override fun iterator(): DoubleIterator {
+            return ValueIterator(map.iterator())
+        }
+
+        private class ValueIterator(private val iterator: Iterator<DoubleDoubleMapEntry>) :
+            DoubleIterator() {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun nextDouble(): Double {
+                return iterator.next().value
+            }
+        }
+    }
+
+    private class KeyCollection(private val map: DoubleDoubleMap) : IDoubleIterable {
+        override fun iterator(): DoubleIterator {
+            return KeyIterator(map.iterator())
+        }
+
+        private class KeyIterator(private val iterator: Iterator<DoubleDoubleMapEntry>) :
+            DoubleIterator() {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun nextDouble(): Double {
+                return iterator.next().key
+            }
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleList.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleList.kt
new file mode 100644
index 000000000..0ccc29045
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleList.kt
@@ -0,0 +1,137 @@
+package alphaTab.collections
+
+import alphaTab.core.toInvariantString
+
+public interface IDoubleIterable : Iterable<Double> {
+    override fun iterator(): DoubleIterator
+}
+
+public class DoubleList : IDoubleIterable {
+    companion object {
+        private const val DefaultCapacity: Int = 4
+    }
+
+    private var _items: DoubleArray
+    private var _size: Int = 0
+
+    public val length: Double get() = _size.toDouble()
+
+    public constructor() {
+        _items = DoubleArray(0)
+    }
+
+    public constructor(size: Int) {
+        _items = DoubleArray(size)
+        _size = size
+    }
+
+    public constructor(vararg elements: Double) {
+        _items = elements
+        _size = elements.size
+    }
+
+    private constructor(elements: DoubleArray, size: Int) {
+        _items = elements
+        _size = size
+    }
+
+    public operator fun get(index: Int): Double {
+        return _items[index]
+    }
+
+    public operator fun set(index: Int, value: Double) {
+        _items[index] = value
+    }
+
+    public fun push(item: Double) {
+        val array = _items
+        val size = _size
+        if (size < array.size) {
+            _size = size + 1
+            array[size] = item
+        } else {
+            addWithResize(item)
+        }
+    }
+
+    private fun addWithResize(item: Double) {
+        val size = _size
+        grow(size + 1)
+        _size = (size + 1)
+        _items[size] = item
+    }
+
+    private fun grow(capacity: Int) {
+        var newCapacity = if (_items.isEmpty()) DefaultCapacity else 2 * _items.size
+        if (newCapacity < capacity) {
+            newCapacity = capacity
+        }
+
+        val newItems = DoubleArray(newCapacity)
+        if (_size > 0) {
+            _items.copyInto(newItems, 0, 0, _size)
+        }
+        _items = newItems
+    }
+
+    public fun join(separator: String): String {
+        return this.map<String> { it.toInvariantString() }.joinToString(separator)
+    }
+
+    public fun <TOut> map(transform: (v: Double) -> TOut): List<TOut> {
+        val mapped = List<TOut>()
+        for (el in this) {
+            mapped.push(transform(el))
+        }
+        return mapped
+    }
+
+    public fun map(transform: (v: Double) -> Double): DoubleList {
+        val mapped = DoubleList(_size)
+        for (i in 0 until _size) {
+            mapped[i] = transform(_items[i])
+        }
+        return mapped
+    }
+
+    public fun reverse(): DoubleList {
+        _items.reverse(0, _size)
+        return this
+    }
+
+    public fun fill(value: Double): DoubleList {
+        _items.fill(value)
+        return this
+    }
+
+    public fun slice(): DoubleList {
+        val copy = DoubleArray(_size) {
+            _items[it]
+        }
+        return DoubleList(copy, _size)
+    }
+
+    public fun sort() {
+        _items.sort(0, _size)
+    }
+
+    public override fun iterator(): DoubleIterator {
+        return Iterator(this)
+    }
+
+    private class Iterator(private val list: DoubleList) : DoubleIterator() {
+        private var _index = 0
+        override fun hasNext(): Boolean {
+            return _index < list._size
+        }
+
+        override fun nextDouble(): Double {
+            if (_index >= list._size) {
+                throw NoSuchElementException("List has no more elements")
+            }
+            val value = list[_index]
+            _index++
+            return value
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleObjectMap.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleObjectMap.kt
new file mode 100644
index 000000000..e812b634b
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/DoubleObjectMap.kt
@@ -0,0 +1,151 @@
+package alphaTab.collections
+
+public open class DoubleObjectMapEntry<TValue> {
+    private var _key: Double
+    public var key: Double
+        get() = _key
+        internal set(value) {
+            _key = value
+        }
+
+    private var _value: TValue
+    public var value: TValue
+        get() = _value
+        internal set(value) {
+            _value = value
+        }
+
+    public operator fun component1(): Double {
+        return _key
+    }
+
+    public operator fun component2(): TValue {
+        return _value
+    }
+
+    public constructor() {
+        _key = 0.0
+        _value = null as TValue
+    }
+
+    public constructor(key: Double, value: TValue) {
+        _key = key
+        _value = value
+    }
+}
+
+public class DoubleObjectMapEntryInternal<TValue> : DoubleObjectMapEntry<TValue>(),
+    IMapEntryInternal {
+    public override var hashCode: Int = 0
+    public override var next: Int = 0
+
+    override fun reset() {
+        key = 0.0
+        value = null as TValue
+    }
+}
+
+public class DoubleObjectMap<TValue> :
+    MapBase<DoubleObjectMapEntry<TValue>, DoubleObjectMapEntryInternal<TValue>> {
+    public constructor()
+    public constructor(iterable: Iterable<DoubleObjectMapEntry<TValue>>) {
+        for (it in iterable) {
+            set(it.key, it.value)
+        }
+    }
+
+    public fun has(key: Double): Boolean {
+        return findEntryInternal(key,
+            { entry, k -> entry.key == k }) >= 0
+    }
+
+    public fun get(key: Double): TValue {
+        val i = findEntryInternal(key,
+            { entry, k -> entry.key == k })
+        if (i >= 0) {
+            return entries[i].value
+        }
+        throw KeyNotFoundException()
+    }
+
+    public fun set(key: Double, value: TValue) {
+        insert(key, value)
+    }
+
+    private fun insert(key: Double, value: TValue) {
+        insertInternal(
+            key, value as Any,
+            { entry, k -> entry.key = k },
+            { entry, v -> entry.value = v as TValue },
+            { entry, k -> entry.key == k }
+        )
+    }
+
+    public fun delete(key: Double) {
+        deleteInternal(key.hashCode())
+    }
+
+    private var _values: ValueCollection<TValue>? = null
+    public fun values(): Iterable<TValue> {
+        _values = _values ?: ValueCollection(this)
+        return _values!!
+    }
+
+    private var _keys: KeyCollection<TValue>? = null
+    public fun keys(): IDoubleIterable {
+        _keys = _keys ?: KeyCollection(this)
+        return _keys!!
+    }
+
+    override fun createEntries(size: Int): Array<DoubleObjectMapEntryInternal<TValue>> {
+        return Array(size) {
+            DoubleObjectMapEntryInternal()
+        }
+    }
+
+    override fun createEntries(
+        size: Int,
+        old: Array<DoubleObjectMapEntryInternal<TValue>>
+    ): Array<DoubleObjectMapEntryInternal<TValue>> {
+        return Array(size) {
+            if (it < old.size) old[it] else DoubleObjectMapEntryInternal()
+        }
+    }
+
+
+    private class ValueCollection<TValue>(private val map: DoubleObjectMap<TValue>) :
+        Iterable<TValue> {
+        override fun iterator(): Iterator<TValue> {
+            return ValueIterator(map.iterator())
+        }
+
+        private class ValueIterator<TValue>(private val iterator: Iterator<DoubleObjectMapEntry<TValue>>) :
+            Iterator<TValue> {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun next(): TValue {
+                return iterator.next().value
+            }
+        }
+    }
+
+    private class KeyCollection<TValue>(private val map: DoubleObjectMap<TValue>) :
+        IDoubleIterable {
+        override fun iterator(): DoubleIterator {
+            return ValueIterator(map.iterator())
+        }
+
+        private class ValueIterator<TValue>(private val iterator: Iterator<DoubleObjectMapEntry<TValue>>) :
+            DoubleIterator() {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun nextDouble(): Double {
+                return iterator.next().key
+            }
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/HashHelpers.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/HashHelpers.kt
new file mode 100644
index 000000000..33a4149a9
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/HashHelpers.kt
@@ -0,0 +1,53 @@
+package alphaTab.collections
+
+import kotlin.math.sqrt
+
+class HashHelpers {
+    companion object {
+        private val _primes: IntArray = intArrayOf(
+            3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
+            1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
+            17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
+            187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
+            1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
+        )
+        private const val hashPrime = 101
+
+        public fun expandPrime(oldSize:Int): Int {
+            val newSize = 2 * oldSize
+            return getPrime(newSize)
+        }
+
+        public fun getPrime(min: Int): Int {
+            for (prime in _primes) {
+                if (prime >= min) {
+                    return prime
+                }
+            }
+
+            //outside of our predefined table.
+            //compute the hard way.
+            var i = min or 1
+            while (i < Int.MAX_VALUE) {
+                if (isPrime(i) && (i - 1) % hashPrime != 0) return i
+                i += 2
+            }
+            return min
+        }
+
+        private fun isPrime(candidate: Int): Boolean {
+            if (candidate and 1 != 0) {
+                val limit = sqrt(candidate.toDouble()).toInt()
+                var divisor = 3
+                while (divisor <= limit) {
+                    if (candidate % divisor == 0) {
+                        return false
+                    }
+                    divisor += 2
+                }
+                return true
+            }
+            return candidate == 2
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/KeyNotFoundException.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/KeyNotFoundException.kt
new file mode 100644
index 000000000..38b0cd82a
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/KeyNotFoundException.kt
@@ -0,0 +1,3 @@
+package alphaTab.collections
+
+internal class KeyNotFoundException : Exception()
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/List.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/List.kt
new file mode 100644
index 000000000..220880f6f
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/List.kt
@@ -0,0 +1,105 @@
+package alphaTab.collections
+
+public class List<T> : Iterable<T> {
+    private val _data: MutableList<T>
+
+    val length: Double
+        get() = _data.size.toDouble()
+
+    public constructor() {
+        _data = ArrayList()
+    }
+
+    public constructor(vararg items: T) {
+        _data = items.toMutableList()
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    public constructor(size: Int) {
+        _data = ArrayList()
+        var remaining = size
+        while (remaining-- > 0) {
+            _data.add(null as T)
+        }
+    }
+
+    public constructor(items: Iterable<T>) {
+        _data = items.toMutableList()
+    }
+
+    private constructor(items: MutableList<T>) {
+        _data = items
+    }
+
+    public override fun iterator(): Iterator<T> {
+        return _data.iterator()
+    }
+
+    public fun push(item: T) {
+        _data.add(item)
+    }
+
+    public operator fun get(index: Int): T {
+        return _data[index]
+    }
+
+    public operator fun set(index: Int, value: T) {
+        _data[index] = value
+    }
+
+    public fun filter(predicate: (T) -> Boolean): List<T> {
+        return List(_data.filter(predicate))
+    }
+
+    public fun indexOf(value: T): Double {
+        return _data.indexOf(value).toDouble()
+    }
+
+    public fun pop(): T {
+        return _data.removeLast()
+    }
+
+    public fun sort(comparison: (a: T, b: T) -> Double) {
+        _data.sortWith { a, b -> comparison(a, b).toInt() }
+    }
+
+    public fun <TOut> map(transform: (v: T) -> TOut): List<TOut> {
+        return List(_data.map(transform))
+    }
+
+    public fun map(transform: (v: T) -> Double): DoubleList {
+        val mapped = DoubleList(_data.size)
+        _data.forEachIndexed { index, item ->
+            mapped[index] = transform(item)
+        }
+        return mapped
+    }
+
+    public fun reverse(): List<T> {
+        _data.reverse()
+        return this
+    }
+
+    public fun slice(): List<T> {
+        return List(ArrayList(_data))
+    }
+
+    public fun slice(start: Double): List<T> {
+        return List(_data.subList(start.toInt(), _data.size))
+    }
+
+    public fun splice(start: Double, deleteCount: Double, vararg newElements: T) {
+        var actualStart = start.toInt()
+        if (actualStart < 0)
+        {
+            actualStart += _data.size
+        }
+
+        _data.subList(start.toInt(), (start + deleteCount).toInt()).clear()
+        _data.addAll(start.toInt(), newElements.toList())
+    }
+
+    public fun join(separator: String): String {
+        return _data.joinToString(separator)
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/Map.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/Map.kt
new file mode 100644
index 000000000..f0559a9a3
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/Map.kt
@@ -0,0 +1,55 @@
+package alphaTab.collections
+
+public data class MapEntry<TKey, TValue>(
+    public val key:TKey,
+    public val value:TValue
+)
+
+public class Map<TKey, TValue> {
+    private val _data: LinkedHashMap<TKey, TValue>
+    
+    public constructor() {
+        _data = LinkedHashMap()
+    }
+
+    public constructor(entries: Iterable<MapEntry<TKey, TValue>>) {
+        _data = LinkedHashMap()
+        _data.putAll(entries.map { Pair(it.key, it.value) })
+    }
+
+    public val size: Double
+        get() = _data.size.toDouble()
+
+    public fun has(key: TKey): Boolean {
+        return _data.containsKey(key)
+    }
+
+    public fun get(key: TKey): TValue {
+        @Suppress("UNCHECKED_CAST")
+        return _data[key] as TValue
+    }
+
+    public fun set(key: TKey, value: TValue) {
+        _data[key] = value
+    }
+
+    public fun delete(key: TKey) {
+        _data.remove(key)
+    }
+
+    public fun values(): Iterable<TValue> {
+        return _data.values
+    }
+
+    public fun keys(): Iterable<TKey> {
+        return _data.keys
+    }
+
+    public fun clear() {
+        _data.clear()
+    }
+
+    public operator fun iterator(): Iterator<MapEntry<TKey, TValue>> {
+        return _data.map { MapEntry(it.key, it.value) }.iterator()
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/MapBase.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/MapBase.kt
new file mode 100644
index 000000000..837b766f9
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/MapBase.kt
@@ -0,0 +1,197 @@
+package alphaTab.collections
+
+public interface IMapEntryInternal {
+    public fun reset()
+    public var hashCode: Int
+    public var next: Int
+}
+
+internal inline fun
+    <reified TEntryType, reified TInternalEntryType : IMapEntryInternal, reified TKey>
+    MapBase<TEntryType, TInternalEntryType>.findEntryInternal(
+    key: TKey,
+    keyEquals: (entry: TInternalEntryType, key: TKey) -> Boolean
+): Int {
+    val buckets = _buckets
+    val entries = this.entries
+    if (buckets != null) {
+        val hashCode = key.hashCode() and 0x7FFFFFFF
+        var i = buckets[hashCode % buckets.size]
+        while (i >= 0) {
+            if (entries[i].hashCode == hashCode && keyEquals(entries[i], key)) {
+                return i
+            }
+
+            i = entries[i].next
+        }
+    }
+    return -1
+}
+
+internal inline fun
+    <reified TEntryType, reified TInternalEntryType : IMapEntryInternal, reified TKey, reified TValue>
+    MapBase<TEntryType, TInternalEntryType>.insertInternal(
+    key: TKey,
+    value: TValue,
+    setKey: (entry: TInternalEntryType, key: TKey) -> Unit,
+    setValue: (entry: TInternalEntryType, value: TValue) -> Unit,
+    keyEquals: (entry: TInternalEntryType, key: TKey) -> Boolean
+) {
+    var buckets = _buckets
+    if (buckets == null) {
+        buckets = initialize(0)
+    }
+
+    val hashCode = key.hashCode() and 0x7FFFFFFF
+    var targetBucket = hashCode % buckets.size
+
+    var i = buckets[targetBucket]
+    while (i >= 0) {
+        if (entries[i].hashCode == hashCode && keyEquals(entries[i], key)) {
+            setValue(entries[i], value)
+            return
+        }
+
+        i = entries[i].next
+    }
+
+    val index: Int
+    if (_freeCount > 0) {
+        index = _freeList
+        _freeList = entries[index].next
+        _freeCount--
+    } else {
+        if (_count == entries.size) {
+            buckets = resize()
+            targetBucket = hashCode % buckets.size
+        }
+        index = _count
+        _count++
+    }
+
+    val entry = entries[index]
+    entry.hashCode = hashCode
+    entry.next = buckets[targetBucket]
+    setKey(entry, key)
+    setValue(entry, value)
+    buckets[targetBucket] = index
+}
+
+
+public abstract class MapBase<TEntryType, TInternalEntryType : IMapEntryInternal> :
+    Iterable<TEntryType> {
+    internal var _count: Int = 0
+    internal var _freeCount: Int = 0
+    internal var _buckets: IntArray? = null
+    internal var _freeList: Int = 0
+
+    internal var entries: Array<TInternalEntryType> = createEntries(0)
+    internal fun initialize(capacity: Int): IntArray {
+        val size = HashHelpers.getPrime(capacity)
+        _buckets = IntArray(size) {
+            -1
+        }
+        entries = createEntries(size)
+        _freeList = -1
+        return _buckets!!
+    }
+
+    internal abstract fun createEntries(size: Int): Array<TInternalEntryType>
+    internal abstract fun createEntries(
+        size: Int,
+        old: Array<TInternalEntryType>
+    ): Array<TInternalEntryType>
+
+    public val size: Double
+        get() = (_count - _freeCount).toDouble()
+
+    internal fun resize(): IntArray {
+        return resize(HashHelpers.expandPrime(_count))
+    }
+
+    private fun resize(newSize: Int): IntArray {
+        val newBuckets = IntArray(newSize) {
+            -1
+        }
+        val newEntries = createEntries(newSize, entries)
+        var i = 0
+        while (i < _count) {
+            if (newEntries[i].hashCode >= 0) {
+                val bucket = newEntries[i].hashCode % newSize
+                newEntries[i].next = newBuckets[bucket]
+                newBuckets[bucket] = i
+            }
+            i++
+        }
+        _buckets = newBuckets
+        entries = newEntries
+        return newBuckets
+    }
+
+    protected fun deleteInternal(keyHashCode: Int) {
+        val buckets = _buckets
+        if (buckets != null) {
+            val hashCode = keyHashCode and 0x7FFFFFFF
+            val bucket = hashCode % buckets.size
+            var last = -1
+            var i = buckets[bucket]
+            while (i >= 0) {
+                if (entries[i].hashCode == hashCode) {
+                    if (last < 0) {
+                        buckets[bucket] = entries[i].next
+                    } else {
+                        entries[last].next = entries[i].next
+                    }
+                    entries[i].hashCode = -1
+                    entries[i].next = _freeList
+                    entries[i].reset()
+                    _freeList = i
+                    _freeCount++
+                    return
+                }
+
+                last = i
+                i = entries[i].next
+            }
+        }
+    }
+
+    public fun clear() {
+        if (_count > 0) {
+            _buckets?.fill(-1)
+            entries = createEntries(0)
+            _freeList = -1
+            _count = 0
+            _freeCount = 0
+        }
+    }
+
+    override fun iterator(): Iterator<TEntryType> {
+        return MapIterator(this)
+    }
+
+    private class MapIterator<TEntryType, TInternalEntryType : IMapEntryInternal>
+        (private val map: MapBase<TEntryType, TInternalEntryType>) : Iterator<TEntryType> {
+        private var _entryIndex = 0
+        private var _index = 0
+
+        override fun hasNext(): Boolean {
+            return _index < map.size
+        }
+
+        override fun next(): TEntryType {
+            while (_entryIndex < map._count) {
+                if (map.entries[_entryIndex].hashCode >= 0) {
+                    val currentValue = map.entries[_entryIndex] as TEntryType
+                    _entryIndex++
+                    _index++
+                    return currentValue
+                }
+                _entryIndex++
+            }
+            throw NoSuchElementException("Map has no more elements")
+        }
+    }
+
+
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/ObjectBooleanMap.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/ObjectBooleanMap.kt
new file mode 100644
index 000000000..9b6b21540
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/ObjectBooleanMap.kt
@@ -0,0 +1,150 @@
+package alphaTab.collections
+
+public open class ObjectBooleanMapEntry<TKey> {
+    private var _key: TKey
+    public var key: TKey
+        get() = _key
+        internal set(value) {
+            _key = value
+        }
+
+    private var _value: Boolean
+    public var value: Boolean
+        get() = _value
+        internal set(value) {
+            _value = value
+        }
+
+
+    public constructor() {
+        _key = null as TKey
+        _value = false
+    }
+
+    public operator fun component1(): TKey {
+        return _key
+    }
+
+    public operator fun component2(): Boolean {
+        return _value
+    }
+
+    public constructor(key: TKey, value: Boolean) {
+        _key = key
+        _value = value
+    }
+}
+
+public class ObjectBooleanMapEntryInternal<TKey> : ObjectBooleanMapEntry<TKey>(),
+    IMapEntryInternal {
+    public override var hashCode: Int = 0
+    public override var next: Int = 0
+
+    override fun reset() {
+        key = null as TKey
+        value = false
+    }
+}
+
+public class ObjectBooleanMap<TKey> :
+    MapBase<ObjectBooleanMapEntry<TKey>, ObjectBooleanMapEntryInternal<TKey>> {
+    public constructor()
+    public constructor(iterable: Iterable<ObjectBooleanMapEntry<TKey>>) {
+        for (it in iterable) {
+            set(it.key, it.value)
+        }
+    }
+
+    public fun has(key: TKey): Boolean {
+        return findEntryInternal(key as Any,
+            { entry, k -> entry.key == (k as TKey) }) >= 0
+    }
+
+    public fun get(key: TKey): Boolean {
+        val i = findEntryInternal(key as Any,
+            { entry, k -> entry.key == (k as TKey) })
+        if (i >= 0) {
+            return entries[i].value
+        }
+        throw KeyNotFoundException()
+    }
+
+    public fun set(key: TKey, value: Boolean) {
+        insert(key, value)
+    }
+
+    private fun insert(key: TKey, value: Boolean) {
+        insertInternal(
+            key as Any, value,
+            { entry, k -> entry.key = k as TKey },
+            { entry, v -> entry.value = v },
+            { entry, k -> entry.key == (k as TKey) }
+        )
+    }
+
+    public fun delete(key: TKey) {
+        deleteInternal(key.hashCode())
+    }
+
+    private var _values: ValueCollection<TKey>? = null
+    public fun values(): IBooleanIterable {
+        _values = _values ?: ValueCollection(this)
+        return _values!!
+    }
+
+    private var _keys: KeyCollection<TKey>? = null
+    public fun keys(): Iterable<TKey> {
+        _keys = _keys ?: KeyCollection(this)
+        return _keys!!
+    }
+
+    override fun createEntries(size: Int): Array<ObjectBooleanMapEntryInternal<TKey>> {
+        return Array(size) {
+            ObjectBooleanMapEntryInternal()
+        }
+    }
+
+    override fun createEntries(
+        size: Int,
+        old: Array<ObjectBooleanMapEntryInternal<TKey>>
+    ): Array<ObjectBooleanMapEntryInternal<TKey>> {
+        return Array(size) {
+            if (it < old.size) old[it] else ObjectBooleanMapEntryInternal()
+        }
+    }
+
+    private class ValueCollection<TKey>(private val map: ObjectBooleanMap<TKey>) :
+        IBooleanIterable {
+        override fun iterator(): BooleanIterator {
+            return ValueIterator(map.iterator())
+        }
+
+        private class ValueIterator<TKey>(private val iterator: Iterator<ObjectBooleanMapEntry<TKey>>) :
+            BooleanIterator() {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun nextBoolean(): Boolean {
+                return iterator.next().value
+            }
+        }
+    }
+
+
+    private class KeyCollection<TKey>(private val map: ObjectBooleanMap<TKey>) : Iterable<TKey> {
+        override fun iterator(): Iterator<TKey> {
+            return KeyIterator(map.iterator())
+        }
+
+        private class KeyIterator<TKey>(private val iterator: Iterator<ObjectBooleanMapEntry<TKey>>) : Iterator<TKey> {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun next(): TKey {
+                return iterator.next().key
+            }
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/ObjectDoubleMap.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/ObjectDoubleMap.kt
new file mode 100644
index 000000000..af420d3cf
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/collections/ObjectDoubleMap.kt
@@ -0,0 +1,142 @@
+package alphaTab.collections
+
+public open class ObjectDoubleMapEntry<TKey> {
+    private var _key: TKey
+    public var key: TKey
+        get() = _key
+        internal set(value) {
+            _key = value
+        }
+
+    private var _value: Double
+    public var value: Double
+        get() = _value
+        internal set(value) {
+            _value = value
+        }
+
+    public constructor() {
+        _key = null as TKey
+        _value = 0.0
+    }
+
+    public constructor(key: TKey, value: Double) {
+        _key = key
+        _value = value
+    }
+}
+
+public class ObjectDoubleMapEntryInternal<TKey> : ObjectDoubleMapEntry<TKey>(),
+    IMapEntryInternal {
+    public override var hashCode: Int = 0
+    public override var next: Int = 0
+
+    override fun reset() {
+        key = null as TKey
+        value = 0.0
+    }
+}
+
+public class ObjectDoubleMap<TKey> :
+    MapBase<ObjectDoubleMapEntry<TKey>, ObjectDoubleMapEntryInternal<TKey>> {
+    public constructor()
+    public constructor(iterable: Iterable<ObjectDoubleMapEntry<TKey>>) {
+        for (it in iterable) {
+            set(it.key, it.value)
+        }
+    }
+
+    public fun has(key: TKey): Boolean {
+        return findEntryInternal(key as Any,
+            { entry, k -> entry.key == (k as TKey) }) >= 0
+    }
+
+    public fun get(key: TKey): Double {
+        val i = findEntryInternal(key as Any,
+            { entry, k -> entry.key == (k as TKey) })
+        if (i >= 0) {
+            return entries[i].value
+        }
+        throw KeyNotFoundException()
+    }
+
+    public fun set(key: TKey, value: Double) {
+        insert(key, value)
+    }
+
+    private fun insert(key: TKey, value: Double) {
+        insertInternal(
+            key as Any, value,
+            { entry, k -> entry.key = k as TKey },
+            { entry, v -> entry.value = v },
+            { entry, k -> entry.key == (k as TKey) }
+        )
+    }
+
+    public fun delete(key: TKey) {
+        deleteInternal(key.hashCode())
+    }
+
+    private var _values: ValueCollection<TKey>? = null
+    public fun values(): IDoubleIterable {
+        _values = _values ?: ValueCollection(this)
+        return _values!!
+    }
+
+    private var _keys: KeyCollection<TKey>? = null
+    public fun keys(): Iterable<TKey> {
+        _keys = _keys ?: KeyCollection(this)
+        return _keys!!
+    }
+
+    override fun createEntries(size: Int): Array<ObjectDoubleMapEntryInternal<TKey>> {
+        return Array(size) {
+            ObjectDoubleMapEntryInternal()
+        }
+    }
+
+    override fun createEntries(
+        size: Int,
+        old: Array<ObjectDoubleMapEntryInternal<TKey>>
+    ): Array<ObjectDoubleMapEntryInternal<TKey>> {
+        return Array(size) {
+            if (it < old.size) old[it] else ObjectDoubleMapEntryInternal()
+        }
+    }
+
+    private class ValueCollection<TKey>(private val map: ObjectDoubleMap<TKey>) :
+        IDoubleIterable {
+        override fun iterator(): DoubleIterator {
+            return ValueIterator(map.iterator())
+        }
+
+        private class ValueIterator<TKey>(private val iterator: Iterator<ObjectDoubleMapEntry<TKey>>) :
+            DoubleIterator() {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun nextDouble(): Double {
+                return iterator.next().value
+            }
+        }
+    }
+
+
+    private class KeyCollection<TKey>(private val map: ObjectDoubleMap<TKey>) : Iterable<TKey> {
+        override fun iterator(): Iterator<TKey> {
+            return KeyIterator(map.iterator())
+        }
+
+        private class KeyIterator<TKey>(private val iterator: Iterator<ObjectDoubleMapEntry<TKey>>) :
+            Iterator<TKey> {
+            override fun hasNext(): Boolean {
+                return iterator.hasNext()
+            }
+
+            override fun next(): TKey {
+                return iterator.next().key
+            }
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/Globals.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/Globals.kt
index 1cf928f22..bec4834d9 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/Globals.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/Globals.kt
@@ -1,11 +1,7 @@
 package alphaTab.core
 
+import alphaTab.collections.List
 import alphaTab.core.ecmaScript.RegExp
-import system.globalization.CultureInfo
-
-expect class LateInitList<T> : MutableList<T> {
-    public constructor(size:Int)
-}
 
 @kotlin.ExperimentalUnsignedTypes
 expect fun UByteArray.decodeToFloatArray(): FloatArray
@@ -16,6 +12,11 @@ expect fun UByteArray.decodeToDoubleArray(): DoubleArray
 @kotlin.ExperimentalUnsignedTypes
 expect fun UByteArray.decodeToString(encoding: String): String
 
+fun <T : Comparable<T> > List<T>.sort(): Unit {
+    this.sort { a,b ->
+        a.compareTo(b).toDouble()
+    }
+}
 fun String.substr(startIndex: Double, length: Double): String {
     return this.substring(startIndex.toInt(), (startIndex + length).toInt())
 }
@@ -24,86 +25,35 @@ fun String.substr(startIndex: Double): String {
     return this.substring(startIndex.toInt())
 }
 
-fun String.replace(pattern: RegExp, replacement: String): String {
-    return pattern.replace(this, replacement)
-}
-
-fun <T> MutableList<T>.sort(comparer: ((a: T, b: T) -> Double)) {
-    this.sortWith { a, b -> comparer(a, b).toInt() }
-}
-
-fun <TIn, TOut> MutableList<TIn>.mapTo(transform: (v: TIn) -> TOut): MutableList<TOut> {
-    return this.map(transform).toMutableList()
-}
-
-fun <T> MutableList<T>.filterBy(predicate: (T) -> Boolean): MutableList<T> {
-    return this.filter(predicate).toMutableList()
-}
-
-fun <T> MutableList<T>.slice(): MutableList<T> {
-    return this.toMutableList()
-}
-
-fun <T> MutableList<T>.slice(start: Double): MutableList<T> {
-    return this.subList(start.toInt(), this.size)
-}
-
-fun <T> MutableList<T>.rev(): MutableList<T> {
-    this.reverse()
-    return this
+fun String.splitBy(separator:String): List<String> {
+    return List(this.split(separator))
 }
 
-fun <T> MutableList<T>.fillWith(value: T): MutableList<T> {
-    this.fill(value)
-    return this
-}
-
-fun <T> MutableList<T>.splice(start: Double, deleteCount: Double, vararg newItems: T) {
-    if (deleteCount > 0) {
-        this.removeAll(this.subList(start.toInt(), (start + deleteCount).toInt()))
-    }
-    this.addAll(start.toInt(), newItems.asList())
+fun String.replace(pattern: RegExp, replacement: String): String {
+    return pattern.replace(this, replacement)
 }
 
-fun <T> MutableList<T>.pop(): T {
-    return this.removeLast()
+fun Iterable<Char>.toCharArray(): CharArray {
+    return this.toList().toCharArray()
 }
 
 fun String.indexOfInDouble(item: String): Double {
     return this.indexOf(item).toDouble()
 }
 
-fun Double.toString(base: Double): String {
+fun Double.toInvariantString(base: Double): String {
     return this.toInt().toString(base.toInt())
 }
 
 expect fun Double.toInvariantString(): String
-
-fun Double.toString(cultureInfo: CultureInfo): String {
-    if (cultureInfo.isInvariant) {
-        return this.toInvariantString()
-    } else {
-        return this.toString()
-    }
+fun IAlphaTabEnum.toInvariantString(): String {
+    return this.toString()
 }
 
 fun String.lastIndexOfInDouble(item: String): Double {
     return this.lastIndexOf(item).toDouble()
 }
 
-fun <T> List<T>.indexOfInDouble(item: T): Double {
-    return this.indexOf(item).toDouble()
-}
-
-@kotlin.jvm.JvmName("joinDouble")
-fun Iterable<Double>.join(separator: String): String {
-    return this.map { it.toInvariantString() }.join(separator)
-}
-
-fun <T> Iterable<T>.join(separator: String): String {
-    return this.joinToString(separator)
-}
-
 operator fun Double.plus(str: String): String {
     return this.toString() + str
 }
@@ -120,10 +70,9 @@ fun String.charCodeAt(index: Double): Double {
     return this[index.toInt()].code.toDouble()
 }
 
-fun String.split(delimiter: String): MutableList<String> {
+fun String.split(delimiter: String): List<String> {
     @Suppress("CHANGING_ARGUMENTS_EXECUTION_ORDER_FOR_NAMED_VARARGS")
-    return this.split(delimiters = arrayOf(delimiter), ignoreCase = false, limit = 0)
-        .toMutableList()
+    return alphaTab.collections.List(this.split(delimiters = arrayOf(delimiter), ignoreCase = false, limit = 0))
 }
 
 fun String.substring(startIndex: Double, endIndex: Double): String {
@@ -191,3 +140,12 @@ class Globals {
         }
     }
 }
+
+public fun List<Char>.toCharArray(): CharArray {
+    val result = CharArray(length.toInt())
+    var index = 0
+    for (element in this) {
+        result[index++] = element
+    }
+    return result
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/TypeHelper.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/TypeHelper.kt
index befd2902b..5a75829a2 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/TypeHelper.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/TypeHelper.kt
@@ -50,5 +50,10 @@ class TypeHelper {
         public fun <K, V> createMapEntry(k: K, v: V): Pair<K, V> {
             return Pair(k, v)
         }
+
+        public fun <T> setInitializer(vararg values:T) : Iterable<T>
+        {
+            return values.map { it }
+        }
     }
 }
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Array.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Array.kt
index 56013def1..11a9d1646 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Array.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Array.kt
@@ -2,11 +2,12 @@ package alphaTab.core.ecmaScript
 
 class Array {
     companion object {
-        public fun <T> from(x: Iterable<T>): MutableList<T> {
-            return x.toMutableList()
+        public fun <T> from(x: Iterable<T>): alphaTab.collections.List<T> {
+            return alphaTab.collections.List(x)
         }
         public fun isArray(x:Any?):Boolean {
-            return x is MutableList<*>
+            return x is alphaTab.collections.List<*>
         }
+
     }
 }
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/DataView.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/DataView.kt
deleted file mode 100644
index 97722e139..000000000
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/DataView.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-package alphaTab.core.ecmaScript
-
-import alphaTab.core.BitConverter
-
-@ExperimentalUnsignedTypes
-class DataView {
-    private val _buffer: ArrayBuffer
-
-    public constructor(buffer: ArrayBuffer) {
-        this._buffer = buffer
-    }
-
-    public fun getUint8(offset:Double): Double {
-        return _buffer.raw[offset.toInt()].toDouble()
-    }
-
-    public fun setUint16(offset: Double, value: Double, littleEndian: Boolean) {
-        BitConverter.put(
-            _buffer.raw.asByteArray(),
-            offset.toInt(),
-            value.toUInt().toUShort(),
-            littleEndian
-        )
-    }
-
-    public fun getInt16(offset: Double, littleEndian: Boolean): Double {
-        return BitConverter.getInt16(_buffer.raw.asByteArray(), offset.toInt(), littleEndian)
-            .toDouble()
-    }
-
-    public fun setInt16(offset: Double, value: Double, littleEndian: Boolean) {
-        BitConverter.put(
-            _buffer.raw.asByteArray(),
-            offset.toInt(),
-            value.toInt().toShort(),
-            littleEndian
-        )
-    }
-
-    public fun getUint32(offset: Double, littleEndian: Boolean): Double {
-        return BitConverter.getUint32(_buffer.raw.asByteArray(), offset.toInt(), littleEndian)
-            .toDouble()
-    }
-
-    public fun getInt32(offset: Double, littleEndian: Boolean): Double {
-        return BitConverter.getInt32(_buffer.raw.asByteArray(), offset.toInt(), littleEndian)
-            .toDouble()
-    }
-
-    public fun setInt32(offset: Double, value: Double, littleEndian: Boolean) {
-        BitConverter.put(_buffer.raw.asByteArray(), offset.toInt(), value.toInt(), littleEndian)
-    }
-
-    public fun getUint16(offset: Double, littleEndian: Boolean): Double {
-        return BitConverter.getUint16(_buffer.raw.asByteArray(), offset.toInt(), littleEndian)
-            .toDouble()
-    }
-
-    public fun setUint8(offset: Double, value: Double) {
-        _buffer.raw[offset.toInt()] = value.toUInt().toUByte()
-    }
-
-    public fun getInt8(offset: Double): Double {
-        return _buffer.raw[offset.toInt()].toByte().toDouble()
-    }
-}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Error.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Error.kt
index cea490747..baa8ad808 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Error.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Error.kt
@@ -1,13 +1,5 @@
 package alphaTab.core.ecmaScript
 
-open class Error : Throwable {
-    public override val message: String
+import kotlin.Exception
 
-    public constructor() : super() {
-        this.message = ""
-    }
-
-    public constructor(msg: String) : super(msg) {
-        this.message = msg;
-    }
-}
+typealias Error = Exception
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Float32Array.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Float32Array.kt
index 7334ea114..8e12e4e4f 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Float32Array.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Float32Array.kt
@@ -23,7 +23,7 @@ public class Float32Array : Iterable<Float> {
         data = x
     }
 
-    public constructor(x: List<Double>) {
+    public constructor(x: Iterable<Double>) {
         this.data = x.map { d -> d.toFloat() }.toFloatArray()
     }
 
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Map.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Map.kt
deleted file mode 100644
index 338b48c0c..000000000
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Map.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-package alphaTab.core.ecmaScript
-
-import kotlin.collections.Map
-
-class Map<TKey, TValue> : Iterable<Map.Entry<TKey, TValue>> {
-    private val _map: LinkedHashMap <TKey, TValue>
-
-    public val size: Double
-        get() {
-            return _map.size.toDouble()
-        }
-
-    public constructor() {
-        _map = LinkedHashMap ()
-    }
-
-    public fun keys(): MutableList<TKey> {
-        return _map.keys.toMutableList()
-    }
-
-    public fun values(): MutableList<TValue> {
-        return _map.values.toMutableList()
-    }
-
-    public constructor(entries: Iterable<Pair<TKey, TValue>>) {
-        _map = LinkedHashMap ()
-        _map.putAll(entries)
-    }
-
-    public fun get(key: TKey): TValue? {
-        return _map[key]
-    }
-
-    public fun set(key: TKey, value: TValue) {
-        _map[key] = value
-    }
-
-    public fun has(key: TKey): Boolean {
-        return _map.containsKey(key)
-    }
-
-    public fun delete(key: TKey) {
-        _map.remove(key)
-    }
-
-    public fun clear() {
-        _map.clear()
-    }
-
-    override fun iterator(): Iterator<Map.Entry<TKey, TValue>> {
-        return _map.iterator()
-    }
-}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Uint8Array.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Uint8Array.kt
index 32286d732..b04e582da 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Uint8Array.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/Uint8Array.kt
@@ -2,51 +2,55 @@ package alphaTab.core.ecmaScript
 
 @ExperimentalUnsignedTypes
 class Uint8Array : Iterable<UByte> {
-    private val _data: UByteArray
+    private val _data: ArrayBuffer
 
     public val buffer: ArrayBuffer
         get() {
-            return ArrayBuffer(_data)
+            return _data
         }
 
-    public constructor(x: List<Double>) {
-        this._data = x.map { d -> d.toInt().toUByte() }.toUByteArray()
+    public constructor(x: Iterable<Double>) {
+        this._data = ArrayBuffer(x.map { d -> d.toInt().toUByte() }.toUByteArray())
     }
 
     public constructor(size: Double) {
-        this._data = UByteArray(size.toInt())
+        this._data = ArrayBuffer(UByteArray(size.toInt()))
     }
 
-    internal constructor(data: UByteArray) {
+    public constructor(data: UByteArray) {
+        this._data = ArrayBuffer(data)
+    }
+
+    public constructor(data:ArrayBuffer) {
         this._data = data
     }
 
     public val length: Double
         get() {
-            return _data.size.toDouble()
+            return _data.raw.size.toDouble()
         }
 
     public operator fun get(idx: Int): Double {
-        return _data[idx].toDouble()
+        return _data.raw[idx].toDouble()
     }
 
     public operator fun get(idx: Double): Double {
-        return _data[idx.toInt()].toDouble()
+        return _data.raw[idx.toInt()].toDouble()
     }
 
     public operator fun set(idx: Int, value: Double) {
-        _data[idx] = value.toInt().toUByte()
+        _data.raw[idx] = value.toInt().toUByte()
     }
 
     public fun set(subarray: Uint8Array, pos: Double) {
-        subarray._data.copyInto(_data, pos.toInt(), 0, subarray._data.size)
+        subarray._data.raw.copyInto(_data.raw, pos.toInt(), 0, subarray._data.raw.size)
     }
 
     override fun iterator(): Iterator<UByte> {
-        return _data.iterator()
+        return _data.raw.iterator()
     }
 
     public fun subarray(begin: Double, end: Double): Uint8Array {
-        return Uint8Array(_data.copyOfRange(begin.toInt(), end.toInt()))
+        return Uint8Array(_data.raw.copyOfRange(begin.toInt(), end.toInt()))
     }
 }
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/ValueTypeMap.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/ValueTypeMap.kt
deleted file mode 100644
index bc7e27e9b..000000000
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/ecmaScript/ValueTypeMap.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-package alphaTab.core.ecmaScript
-
-import kotlin.collections.Map
-
-class ValueTypeMap<TKey, TValue> : Iterable<Map.Entry<TKey, TValue>> {
-    private val _map: HashMap<TKey, TValue>
-
-    public val size: Double
-        get() {
-            return _map.size.toDouble()
-        }
-
-    public constructor() {
-        _map = HashMap()
-    }
-
-    public constructor(entries: Iterable<Pair<TKey, TValue>>) {
-        _map = HashMap()
-        _map.putAll(entries)
-    }
-
-    public fun get(key: TKey): TValue? {
-        return _map[key]
-    }
-
-    public fun set(key: TKey, value: TValue) {
-        _map[key] = value
-    }
-
-    public fun has(key: TKey): Boolean {
-        return _map.containsKey(key)
-    }
-
-    public fun delete(key: TKey) {
-        _map.remove(key)
-    }
-
-    public fun clear() {
-        _map.clear()
-    }
-
-    override fun iterator(): Iterator<Map.Entry<TKey, TValue>> {
-        return _map.iterator()
-    }
-}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/io/JsonHelperPartials.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/io/JsonHelperPartials.kt
index d6ebb8b98..1774779c4 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/io/JsonHelperPartials.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/io/JsonHelperPartials.kt
@@ -16,7 +16,7 @@ internal open class JsonHelperPartials {
                 }
             }
 
-            if (o is alphaTab.core.ecmaScript.Map<*, *>) {
+            if (o is alphaTab.collections.Map<*, *>) {
                 for (kvp in o) {
                     func(kvp.value, (kvp.key!!) as String)
                 }
@@ -30,7 +30,7 @@ internal open class JsonHelperPartials {
                 }
             }
 
-            if (o is alphaTab.core.ecmaScript.Map<*, *>) {
+            if (o is alphaTab.collections.Map<*, *>) {
                 for (kvp in o) {
                     func(kvp.value, (kvp.key!!) as String)
                 }
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/io/TypeConversions.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/io/TypeConversions.kt
new file mode 100644
index 000000000..dd19f1247
--- /dev/null
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/io/TypeConversions.kt
@@ -0,0 +1,16 @@
+package alphaTab.io
+
+import alphaTab.core.ecmaScript.Uint8Array
+
+expect class TypeConversions {
+    companion object {
+        public fun float64ToBytes(v: Double): Uint8Array
+        public fun bytesToFloat64(bytes: Uint8Array): Double
+        public fun uint16ToInt16(v: Double): Double
+        public fun int16ToUint32(v: Double): Double
+        public fun int32ToUint16(v: Double): Double
+        public fun int32ToInt16(v: Double): Double
+        public fun int32ToUint32(v: Double): Double
+        public fun uint8ToInt8(v: Double): Double
+    }
+}
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/Lazy.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/util/Lazy.kt
similarity index 94%
rename from src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/Lazy.kt
rename to src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/util/Lazy.kt
index 83e573c95..ef73fe6b0 100644
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/core/Lazy.kt
+++ b/src.kotlin/alphaTab/src/commonMain/kotlin/alphaTab/util/Lazy.kt
@@ -1,4 +1,4 @@
-package alphaTab.core
+package alphaTab.util
 
 internal object UninitializedValue
 
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/system/Action.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/system/Action.kt
deleted file mode 100644
index 159a5a1a0..000000000
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/system/Action.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package system
-
-typealias Action = () -> Unit;
diff --git a/src.kotlin/alphaTab/src/commonMain/kotlin/system/globalization/CultureInfo.kt b/src.kotlin/alphaTab/src/commonMain/kotlin/system/globalization/CultureInfo.kt
deleted file mode 100644
index b9a6fae31..000000000
--- a/src.kotlin/alphaTab/src/commonMain/kotlin/system/globalization/CultureInfo.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package system.globalization
-
-class CultureInfo {
-    companion object {
-        public var invariantCulture = CultureInfo(true)
-    }
-
-    public val isInvariant:Boolean;
-
-    public constructor(isInvariant:Boolean) {
-        this.isInvariant = isInvariant;
-    }
-}
diff --git a/src.kotlin/alphaTab/src/jvmCommon/kotlin/alphaTab/core/GlobalsImpl.kt b/src.kotlin/alphaTab/src/jvmCommon/kotlin/alphaTab/core/GlobalsImpl.kt
index aa10e9087..f9db44285 100644
--- a/src.kotlin/alphaTab/src/jvmCommon/kotlin/alphaTab/core/GlobalsImpl.kt
+++ b/src.kotlin/alphaTab/src/jvmCommon/kotlin/alphaTab/core/GlobalsImpl.kt
@@ -7,14 +7,6 @@ import java.text.DecimalFormat
 import java.text.NumberFormat
 import java.util.*
 
-actual class LateInitList<T> : java.util.ArrayList<T>, MutableList<T> {
-    @Suppress("UNCHECKED_CAST")
-    public actual constructor(size: Int) : super(arrayOfNulls<Any>(size).toList() as List<T>) {
-
-    }
-}
-
-
 @ExperimentalUnsignedTypes
 actual fun UByteArray.decodeToFloatArray(): FloatArray {
     val fb = ByteBuffer.wrap(this.toByteArray()).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer();
diff --git a/src.kotlin/alphaTab/src/jvmCommon/kotlin/alphaTab/io/TypeConversions.kt b/src.kotlin/alphaTab/src/jvmCommon/kotlin/alphaTab/io/TypeConversions.kt
new file mode 100644
index 000000000..210d796ee
--- /dev/null
+++ b/src.kotlin/alphaTab/src/jvmCommon/kotlin/alphaTab/io/TypeConversions.kt
@@ -0,0 +1,60 @@
+package alphaTab.io
+
+import alphaTab.core.ecmaScript.Uint8Array
+
+@ExperimentalUnsignedTypes
+actual class TypeConversions {
+    actual companion object {
+        public actual fun float64ToBytes(v: Double): Uint8Array {
+            val l = java.lang.Double.doubleToLongBits(v);
+            return Uint8Array(
+                ubyteArrayOf(
+                    ((l shl 56) and 0xFF).toUByte(),
+                    ((l shl 48) and 0xFF).toUByte(),
+                    ((l shl 40) and 0xFF).toUByte(),
+                    ((l shl 32) and 0xFF).toUByte(),
+                    ((l shl 24) and 0xFF).toUByte(),
+                    ((l shl 16) and 0xFF).toUByte(),
+                    ((l shl 8) and 0xFF).toUByte(),
+                    ((l shl 0) and 0xFF).toUByte()
+                )
+            )
+        }
+
+        public actual fun bytesToFloat64(bytes: Uint8Array): Double {
+            val l = (bytes.buffer.raw[0].toLong() shl 56) or
+                (bytes.buffer.raw[1].toLong() shl 48) or
+                (bytes.buffer.raw[2].toLong() shl 40) or
+                (bytes.buffer.raw[3].toLong() shl 32) or
+                (bytes.buffer.raw[4].toLong() shl 24) or
+                (bytes.buffer.raw[5].toLong() shl 16) or
+                (bytes.buffer.raw[6].toLong() shl 8) or
+                (bytes.buffer.raw[7].toLong() shl 0)
+            return java.lang.Double.longBitsToDouble(l);
+        }
+
+        public actual fun uint16ToInt16(v: Double): Double {
+            return v.toUInt().toUShort().toDouble()
+        }
+
+        public actual fun int16ToUint32(v: Double): Double {
+            return v.toInt().toShort().toUInt().toDouble()
+        }
+
+        public actual fun int32ToUint16(v: Double): Double {
+            return v.toInt().toUShort().toDouble()
+        }
+
+        public actual fun int32ToInt16(v: Double): Double {
+            return v.toInt().toShort().toDouble()
+        }
+
+        public actual fun int32ToUint32(v: Double): Double {
+            return v.toInt().toUInt().toDouble()
+        }
+
+        public actual fun uint8ToInt8(v: Double): Double {
+            return v.toUInt().toUByte().toInt().toDouble()
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/jvmMain/kotlin/alphaTab/EnvironmentPartials.kt b/src.kotlin/alphaTab/src/jvmMain/kotlin/alphaTab/EnvironmentPartials.kt
index 9455656bb..cb0cc0433 100644
--- a/src.kotlin/alphaTab/src/jvmMain/kotlin/alphaTab/EnvironmentPartials.kt
+++ b/src.kotlin/alphaTab/src/jvmMain/kotlin/alphaTab/EnvironmentPartials.kt
@@ -5,7 +5,7 @@ import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalUnsignedTypes
 @ExperimentalContracts
-internal actual fun createPlatformSpecificRenderEngines(engines: alphaTab.core.ecmaScript.Map<String, RenderEngineFactory>) {
+internal actual fun createPlatformSpecificRenderEngines(engines: alphaTab.collections.Map<String, RenderEngineFactory>) {
     engines.set(
         "skia",
         RenderEngineFactory(true) { SkiaCanvas() }
diff --git a/src.kotlin/alphaTab/src/jvmMain/kotlin/alphaTab/platform/jvm/SkiaCanvas.kt b/src.kotlin/alphaTab/src/jvmMain/kotlin/alphaTab/platform/jvm/SkiaCanvas.kt
index a84395653..877beceb3 100644
--- a/src.kotlin/alphaTab/src/jvmMain/kotlin/alphaTab/platform/jvm/SkiaCanvas.kt
+++ b/src.kotlin/alphaTab/src/jvmMain/kotlin/alphaTab/platform/jvm/SkiaCanvas.kt
@@ -2,6 +2,7 @@ package alphaTab.platform.jvm
 
 import alphaTab.Settings
 import alphaTab.core.BitConverter
+import alphaTab.core.toCharArray
 import alphaTab.model.Color
 import alphaTab.model.Font
 import alphaTab.model.MusicFontSymbol
@@ -413,19 +414,19 @@ public class SkiaCanvas : ICanvas {
         symbol: MusicFontSymbol,
         centerAtPosition: Boolean?
     ) {
-        fillMusicFontSymbols(x, y, scale, mutableListOf(symbol), centerAtPosition)
+        fillMusicFontSymbols(x, y, scale, alphaTab.collections.List(symbol), centerAtPosition)
     }
 
     override fun fillMusicFontSymbols(
         x: Double,
         y: Double,
         scale: Double,
-        symbols: MutableList<MusicFontSymbol>,
+        symbols: alphaTab.collections.List<MusicFontSymbol>,
         centerAtPosition: Boolean?
     ) {
         val s = String(symbols
             .filter { it != MusicFontSymbol.None }
-            .map { it.value.toChar() }
+            .map<Char> { it.value.toChar() }
             .toCharArray()
         )
 
diff --git a/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/TestPlatformPartials.kt b/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/TestPlatformPartials.kt
index 3b142140a..40446a012 100644
--- a/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/TestPlatformPartials.kt
+++ b/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/TestPlatformPartials.kt
@@ -42,9 +42,12 @@ class TestPlatformPartials {
             }
         }
 
-        public fun listDirectory(path:String): MutableList<String> {
+        public fun listDirectory(path:String): alphaTab.collections.List<String> {
             val dirPath = Path.of(projectRoot, path)
-            return dirPath.toFile().listFiles()?.map { it.name }?.toMutableList() ?: mutableListOf()
+            return alphaTab.collections.List(dirPath.toFile()
+                .listFiles()
+                ?.filter { it.isFile }
+                ?.map { it.name } ?: emptyList())
         }
     }
 }
diff --git a/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/model/ComparisonHelpersPartials.kt b/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/model/ComparisonHelpersPartials.kt
new file mode 100644
index 000000000..f822a85e3
--- /dev/null
+++ b/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/model/ComparisonHelpersPartials.kt
@@ -0,0 +1,38 @@
+package alphaTab.model
+
+import alphaTab.collections.DoubleList
+import alphaTab.test.Globals
+import kotlin.contracts.ExperimentalContracts
+
+@ExperimentalUnsignedTypes
+@ExperimentalContracts
+class ComparisonHelpersPartials {
+    companion object {
+        public fun compareObjects(expected: Any?, actual: Any?, path: String, ignoreKeys: alphaTab.collections.List<String>?): Boolean {
+            if (actual is DoubleList && expected is DoubleList) {
+                if (actual.length != expected.length) {
+                    Globals.fail("""Double Array Length mismatch on hierarchy: ${path}, ${actual.length} != ${expected.length}""")
+                    return false
+                } else {
+                    var i = 0
+                    while (i < actual.length) {
+                        try {
+                            if (alphaTab.core.ecmaScript.Math.abs((actual[i]) - (expected[i])) >= 0.000001)
+                            {
+                                Globals.fail("""Number mismatch on hierarchy: ${path}[${i}], '${actual}' != '${expected}'""")
+                                return false
+                            }
+                        } finally {
+                            i++
+                        }
+                    }
+                }
+
+                return true
+            }
+
+            Globals.fail("cannot compare unknown object types expected[${actual?.javaClass?.packageName}.${actual?.javaClass?.name}] expected[${expected?.javaClass?.packageName}.${expected?.javaClass?.name}]');            }")
+            return false
+        }
+    }
+}
diff --git a/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/test/Globals.kt b/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/test/Globals.kt
index cbbd68b66..b6bcc17da 100644
--- a/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/test/Globals.kt
+++ b/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/test/Globals.kt
@@ -11,6 +11,10 @@ class Globals {
         public fun fail(message: Any?) {
             Assert.fail(message.toString())
         }
+
+        public fun fail(message: Throwable) {
+            Assert.fail(message.toString() + message.stackTraceToString())
+        }
     }
 }
 
diff --git a/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/visualTests/VisualTestHelperPartials.kt b/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/visualTests/VisualTestHelperPartials.kt
index bce4a0b07..174441b90 100644
--- a/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/visualTests/VisualTestHelperPartials.kt
+++ b/src.kotlin/alphaTab/src/jvmTest/kotlin/alphaTab/visualTests/VisualTestHelperPartials.kt
@@ -3,6 +3,7 @@ package alphaTab.visualTests
 import alphaTab.Settings
 import alphaTab.TestPlatform
 import alphaTab.TestPlatformPartials
+import alphaTab.collections.DoubleList
 import alphaTab.core.ecmaScript.Uint8Array
 import alphaTab.core.toInvariantString
 import alphaTab.importer.AlphaTexImporter
@@ -30,7 +31,7 @@ class VisualTestHelperPartials {
         public fun runVisualTest(
             inputFile: String,
             settings: Settings? = null,
-            tracks: MutableList<Double>? = null,
+            tracks: DoubleList? = null,
             message: String? = null,
             tolerancePercent: Double = 1.0,
             triggerResize: Boolean = false
@@ -59,7 +60,7 @@ class VisualTestHelperPartials {
             tex: String,
             referenceFileName: String,
             settings: Settings? = null,
-            tracks: MutableList<Double>? = null,
+            tracks: DoubleList? = null,
             message: String? = null,
             tolerancePercent: Double = 1.0
         ) {
@@ -86,13 +87,13 @@ class VisualTestHelperPartials {
             score: Score,
             referenceFileName: String,
             settings: Settings? = null,
-            tracks: MutableList<Double>? = null,
+            tracks: DoubleList? = null,
             message: String? = null,
             tolerancePercent: Double = 1.0,
             triggerResize: Boolean = false
         ) {
             val actualSettings = settings ?: Settings()
-            val actualTracks = tracks ?: ArrayList()
+            val actualTracks = tracks ?: DoubleList()
 
             actualSettings.core.engine = "skia"
             actualSettings.core.enableLazyLoading = false
@@ -166,7 +167,7 @@ class VisualTestHelperPartials {
                 }
             }
 
-            if (waitHandle.tryAcquire(2000, TimeUnit.MILLISECONDS)) {
+            if (waitHandle.tryAcquire(100000, TimeUnit.MILLISECONDS)) {
                 if (error != null) {
                     Assert.fail("Rendering failed with error $error ${error?.stackTraceToString()}")
                 } else {
diff --git a/src/AlphaTabApiBase.ts b/src/AlphaTabApiBase.ts
index 909477996..25d7793b6 100644
--- a/src/AlphaTabApiBase.ts
+++ b/src/AlphaTabApiBase.ts
@@ -384,7 +384,7 @@ export class AlphaTabApiBase<TSettings> {
         if (this.uiFacade.canRender) {
             // when font is finally loaded, start rendering
             this.renderer.width = this.container.width;
-            this.renderer.renderScore(this.score!, this._trackIndexes!);
+            this.renderer.renderScore(this.score, this._trackIndexes);
         } else {
             this.uiFacade.canRenderChanged.on(() => this.render());
         }
diff --git a/src/AlphaTabError.ts b/src/AlphaTabError.ts
index 5a2d0c1fb..eb450b615 100644
--- a/src/AlphaTabError.ts
+++ b/src/AlphaTabError.ts
@@ -8,8 +8,8 @@ export class AlphaTabError extends Error {
     public inner: Error | null;
     public type: AlphaTabErrorType;
     
-    public constructor(type: AlphaTabErrorType, message: string = "", inner?: Error) {
-        super(message);
+    public constructor(type: AlphaTabErrorType, message: string | null = "", inner?: Error) {
+        super(message ?? "");
         this.type = type;
         this.inner = inner ?? null;
         Object.setPrototypeOf(this, AlphaTabError.prototype);
diff --git a/src/exporter/GpifWriter.ts b/src/exporter/GpifWriter.ts
index 3263e3a81..e39ecb322 100644
--- a/src/exporter/GpifWriter.ts
+++ b/src/exporter/GpifWriter.ts
@@ -571,8 +571,8 @@ export class GpifWriter {
     }
 
     private writeBend(properties: XmlNode, note: Note) {
-        if (note.hasBend && note.bendPoints.length <= 4) {
-            this.writeStandardBend(properties, note.bendPoints);
+        if (note.hasBend && note.bendPoints!.length <= 4) {
+            this.writeStandardBend(properties, note.bendPoints!);
         }
     }
 
@@ -830,8 +830,8 @@ export class GpifWriter {
     }
 
     private writeWhammyNode(parent: XmlNode, beat: Beat) {
-        if (beat.hasWhammyBar && beat.whammyBarPoints.length <= 4) {
-            this.writeStandardWhammy(parent, beat.whammyBarPoints);
+        if (beat.hasWhammyBar && beat.whammyBarPoints!.length <= 4) {
+            this.writeStandardWhammy(parent, beat.whammyBarPoints!);
         }
     }
 
@@ -1184,110 +1184,113 @@ export class GpifWriter {
         diagramCollectionProperty.attributes.set('name', name);
         const diagramCollectionItems = diagramCollectionProperty.addElement('Items');
 
-        for (const [id, chord] of staff.chords) {
-            const diagramCollectionItem = diagramCollectionItems.addElement('Item');
-            diagramCollectionItem.attributes.set('id', id);
-            diagramCollectionItem.attributes.set('name', chord.name);
-
-            const diagram = diagramCollectionItem.addElement('Diagram');
-            diagram.attributes.set('stringCount', chord.strings.length.toString());
-            diagram.attributes.set('fretCount', '5');
-            diagram.attributes.set('baseFret', (chord.firstFret - 1).toString());
-            diagram.attributes.set('barStates', chord.strings.map(_ => '1').join(' '));
-
-            const frets: number[] = [];
-            const fretToStrings = new Map<number, number[]>();
-
-            for (let i = 0; i < chord.strings.length; i++) {
-                let chordFret = chord.strings[i];
-                if (chordFret !== -1) {
-                    const fretNode = diagram.addElement('Fret');
-                    const chordString = (chord.strings.length - 1 - i);
-                    fretNode.attributes.set('string', chordString.toString());
-                    fretNode.attributes.set('fret', (chordFret - chord.firstFret + 1).toString());
-                    if (!fretToStrings.has(chordFret)) {
-                        fretToStrings.set(chordFret, []);
-                        frets.push(chordFret);
+        const sc = staff.chords;
+        if (sc) {
+            for (const [id, chord] of sc) {
+                const diagramCollectionItem = diagramCollectionItems.addElement('Item');
+                diagramCollectionItem.attributes.set('id', id);
+                diagramCollectionItem.attributes.set('name', chord.name);
+
+                const diagram = diagramCollectionItem.addElement('Diagram');
+                diagram.attributes.set('stringCount', chord.strings.length.toString());
+                diagram.attributes.set('fretCount', '5');
+                diagram.attributes.set('baseFret', (chord.firstFret - 1).toString());
+                diagram.attributes.set('barStates', chord.strings.map(_ => '1').join(' '));
+
+                const frets: number[] = [];
+                const fretToStrings = new Map<number, number[]>();
+
+                for (let i = 0; i < chord.strings.length; i++) {
+                    let chordFret = chord.strings[i];
+                    if (chordFret !== -1) {
+                        const fretNode = diagram.addElement('Fret');
+                        const chordString = (chord.strings.length - 1 - i);
+                        fretNode.attributes.set('string', chordString.toString());
+                        fretNode.attributes.set('fret', (chordFret - chord.firstFret + 1).toString());
+                        if (!fretToStrings.has(chordFret)) {
+                            fretToStrings.set(chordFret, []);
+                            frets.push(chordFret);
+                        }
+                        fretToStrings.get(chordFret)!.push(chordString);
                     }
-                    fretToStrings.get(chordFret)!.push(chordString);
                 }
-            }
 
-            frets.sort();
-
-            // try to rebuild the barre frets
-            const fingering = diagram.addElement('Fingering');
-            if (chord.barreFrets.length > 0) {
-                const fingers = [
-                    Fingers.LittleFinger,
-                    Fingers.AnnularFinger,
-                    Fingers.MiddleFinger,
-                    Fingers.IndexFinger,
-                ];
-
-                for (const fret of frets) {
-                    const fretStrings = fretToStrings.get(fret)!;
-                    if (fretStrings.length > 1 && chord.barreFrets.indexOf(fret) >= 0) {
-                        const finger = fingers.length > 0 ? fingers.pop() : Fingers.IndexFinger;
-                        for (const fretString of fretStrings) {
-                            const position = fingering.addElement('Position');
-                            switch (finger) {
-                                case Fingers.LittleFinger:
-                                    position.attributes.set('finger', 'Pinky');
-                                    break;
-                                case Fingers.AnnularFinger:
-                                    position.attributes.set('finger', 'Ring');
-                                    break;
-                                case Fingers.MiddleFinger:
-                                    position.attributes.set('finger', 'Middle');
-                                    break;
-                                case Fingers.IndexFinger:
-                                    position.attributes.set('finger', 'Index');
-                                    break;
+                frets.sort();
+
+                // try to rebuild the barre frets
+                const fingering = diagram.addElement('Fingering');
+                if (chord.barreFrets.length > 0) {
+                    const fingers = [
+                        Fingers.LittleFinger,
+                        Fingers.AnnularFinger,
+                        Fingers.MiddleFinger,
+                        Fingers.IndexFinger,
+                    ];
+
+                    for (const fret of frets) {
+                        const fretStrings = fretToStrings.get(fret)!;
+                        if (fretStrings.length > 1 && chord.barreFrets.indexOf(fret) >= 0) {
+                            const finger = fingers.length > 0 ? fingers.pop() : Fingers.IndexFinger;
+                            for (const fretString of fretStrings) {
+                                const position = fingering.addElement('Position');
+                                switch (finger) {
+                                    case Fingers.LittleFinger:
+                                        position.attributes.set('finger', 'Pinky');
+                                        break;
+                                    case Fingers.AnnularFinger:
+                                        position.attributes.set('finger', 'Ring');
+                                        break;
+                                    case Fingers.MiddleFinger:
+                                        position.attributes.set('finger', 'Middle');
+                                        break;
+                                    case Fingers.IndexFinger:
+                                        position.attributes.set('finger', 'Index');
+                                        break;
+                                }
+                                position.attributes.set('fret', (fret - chord.firstFret + 1).toString());
+                                position.attributes.set('string', fretString.toString());
                             }
-                            position.attributes.set('fret', (fret - chord.firstFret + 1).toString());
-                            position.attributes.set('string', fretString.toString());
                         }
                     }
                 }
-            }
 
 
-            const showName = diagram.addElement('Property');
-            showName.attributes.set('name', 'ShowName');
-            showName.attributes.set('type', 'bool');
-            showName.attributes.set('value', chord.showName ? "true" : "false");
+                const showName = diagram.addElement('Property');
+                showName.attributes.set('name', 'ShowName');
+                showName.attributes.set('type', 'bool');
+                showName.attributes.set('value', chord.showName ? "true" : "false");
 
-            const showDiagram = diagram.addElement('Property');
-            showDiagram.attributes.set('name', 'ShowDiagram');
-            showDiagram.attributes.set('type', 'bool');
-            showDiagram.attributes.set('value', chord.showDiagram ? "true" : "false");
+                const showDiagram = diagram.addElement('Property');
+                showDiagram.attributes.set('name', 'ShowDiagram');
+                showDiagram.attributes.set('type', 'bool');
+                showDiagram.attributes.set('value', chord.showDiagram ? "true" : "false");
 
-            const showFingering = diagram.addElement('Property');
-            showFingering.attributes.set('name', 'ShowFingering');
-            showFingering.attributes.set('type', 'bool');
-            showFingering.attributes.set('value', chord.showFingering ? "true" : "false");
+                const showFingering = diagram.addElement('Property');
+                showFingering.attributes.set('name', 'ShowFingering');
+                showFingering.attributes.set('type', 'bool');
+                showFingering.attributes.set('value', chord.showFingering ? "true" : "false");
 
 
-            // TODO Chord details
-            const chordNode = diagram.addElement('Chord');
-            const keyNoteNode = chordNode.addElement('KeyNote');
-            keyNoteNode.attributes.set('step', 'C');
-            keyNoteNode.attributes.set('accidental', 'Natural');
+                // TODO Chord details
+                const chordNode = diagram.addElement('Chord');
+                const keyNoteNode = chordNode.addElement('KeyNote');
+                keyNoteNode.attributes.set('step', 'C');
+                keyNoteNode.attributes.set('accidental', 'Natural');
 
-            const bassNoteNode = chordNode.addElement('BassNote');
-            bassNoteNode.attributes.set('step', 'C');
-            bassNoteNode.attributes.set('accidental', 'Natural');
+                const bassNoteNode = chordNode.addElement('BassNote');
+                bassNoteNode.attributes.set('step', 'C');
+                bassNoteNode.attributes.set('accidental', 'Natural');
 
-            const degree1Node = chordNode.addElement('Degree');
-            degree1Node.attributes.set('interval', 'Third');
-            degree1Node.attributes.set('alteration', 'Major');
-            degree1Node.attributes.set('omitted', 'false');
+                const degree1Node = chordNode.addElement('Degree');
+                degree1Node.attributes.set('interval', 'Third');
+                degree1Node.attributes.set('alteration', 'Major');
+                degree1Node.attributes.set('omitted', 'false');
 
-            const degree2Node = chordNode.addElement('Degree');
-            degree2Node.attributes.set('interval', 'Fifth');
-            degree2Node.attributes.set('alteration', 'Perfect');
-            degree2Node.attributes.set('omitted', 'false');
+                const degree2Node = chordNode.addElement('Degree');
+                degree2Node.attributes.set('interval', 'Fifth');
+                degree2Node.attributes.set('alteration', 'Perfect');
+                degree2Node.attributes.set('omitted', 'false');
+            }
         }
     }
 
@@ -1531,13 +1534,14 @@ export class GpifWriter {
     }
 
     private writeFermatas(parent: XmlNode, masterBar: MasterBar) {
-        if (masterBar.fermata.size === 0) {
+        const fermataCount = (masterBar.fermata?.size ?? 0);
+        if (fermataCount === 0) {
             return;
         }
 
-        if (masterBar.fermata.size > 0) {
+        if (fermataCount > 0) {
             const fermatas = parent.addElement('Fermatas');
-            for (const [offset, fermata] of masterBar.fermata) {
+            for (const [offset, fermata] of masterBar.fermata!) {
                 this.writeFermata(fermatas, offset, fermata);
             }
         }
diff --git a/src/generated/CoreSettingsSerializer.ts b/src/generated/CoreSettingsSerializer.ts
index c24ad76b2..18f54d8e3 100644
--- a/src/generated/CoreSettingsSerializer.ts
+++ b/src/generated/CoreSettingsSerializer.ts
@@ -19,20 +19,20 @@ export class CoreSettingsSerializer {
         } 
         const o = new Map<string, unknown>(); 
         /*@target web*/
-        o.set("scriptFile", obj.scriptFile); 
+        o.set("scriptfile", obj.scriptFile); 
         /*@target web*/
-        o.set("fontDirectory", obj.fontDirectory); 
+        o.set("fontdirectory", obj.fontDirectory); 
         /*@target web*/
         o.set("file", obj.file); 
         /*@target web*/
         o.set("tex", obj.tex); 
         /*@target web*/
         o.set("tracks", obj.tracks); 
-        o.set("enableLazyLoading", obj.enableLazyLoading); 
+        o.set("enablelazyloading", obj.enableLazyLoading); 
         o.set("engine", obj.engine); 
-        o.set("logLevel", obj.logLevel as number); 
-        o.set("useWorkers", obj.useWorkers); 
-        o.set("includeNoteBounds", obj.includeNoteBounds); 
+        o.set("loglevel", obj.logLevel as number); 
+        o.set("useworkers", obj.useWorkers); 
+        o.set("includenotebounds", obj.includeNoteBounds); 
         return o; 
     }
     public static setProperty(obj: CoreSettings, property: string, v: unknown): boolean {
diff --git a/src/generated/DisplaySettingsSerializer.ts b/src/generated/DisplaySettingsSerializer.ts
index e29db092a..627d570e2 100644
--- a/src/generated/DisplaySettingsSerializer.ts
+++ b/src/generated/DisplaySettingsSerializer.ts
@@ -21,13 +21,13 @@ export class DisplaySettingsSerializer {
         } 
         const o = new Map<string, unknown>(); 
         o.set("scale", obj.scale); 
-        o.set("stretchForce", obj.stretchForce); 
-        o.set("layoutMode", obj.layoutMode as number); 
-        o.set("staveProfile", obj.staveProfile as number); 
-        o.set("barsPerRow", obj.barsPerRow); 
-        o.set("startBar", obj.startBar); 
-        o.set("barCount", obj.barCount); 
-        o.set("barCountPerPartial", obj.barCountPerPartial); 
+        o.set("stretchforce", obj.stretchForce); 
+        o.set("layoutmode", obj.layoutMode as number); 
+        o.set("staveprofile", obj.staveProfile as number); 
+        o.set("barsperrow", obj.barsPerRow); 
+        o.set("startbar", obj.startBar); 
+        o.set("barcount", obj.barCount); 
+        o.set("barcountperpartial", obj.barCountPerPartial); 
         o.set("resources", RenderingResourcesSerializer.toJson(obj.resources)); 
         o.set("padding", obj.padding); 
         return o; 
diff --git a/src/generated/ImporterSettingsSerializer.ts b/src/generated/ImporterSettingsSerializer.ts
index 1bdfc5177..e904e7052 100644
--- a/src/generated/ImporterSettingsSerializer.ts
+++ b/src/generated/ImporterSettingsSerializer.ts
@@ -18,8 +18,8 @@ export class ImporterSettingsSerializer {
         } 
         const o = new Map<string, unknown>(); 
         o.set("encoding", obj.encoding); 
-        o.set("mergePartGroupsInMusicXml", obj.mergePartGroupsInMusicXml); 
-        o.set("beatTextAsLyrics", obj.beatTextAsLyrics); 
+        o.set("mergepartgroupsinmusicxml", obj.mergePartGroupsInMusicXml); 
+        o.set("beattextaslyrics", obj.beatTextAsLyrics); 
         return o; 
     }
     public static setProperty(obj: ImporterSettings, property: string, v: unknown): boolean {
diff --git a/src/generated/NotationSettingsSerializer.ts b/src/generated/NotationSettingsSerializer.ts
index a817bf41b..1c97325c6 100644
--- a/src/generated/NotationSettingsSerializer.ts
+++ b/src/generated/NotationSettingsSerializer.ts
@@ -21,23 +21,23 @@ export class NotationSettingsSerializer {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("notationMode", obj.notationMode as number); 
-        o.set("fingeringMode", obj.fingeringMode as number); 
+        o.set("notationmode", obj.notationMode as number); 
+        o.set("fingeringmode", obj.fingeringMode as number); 
         {
             const m = new Map<string, unknown>();
             o.set("elements", m);
-            for (const [k, v] of obj.elements) {
+            for (const [k, v] of obj.elements!) {
                 m.set(k.toString(), v);
             }
         } 
-        o.set("rhythmMode", obj.rhythmMode as number); 
-        o.set("rhythmHeight", obj.rhythmHeight); 
-        o.set("transpositionPitches", obj.transpositionPitches); 
-        o.set("displayTranspositionPitches", obj.displayTranspositionPitches); 
-        o.set("smallGraceTabNotes", obj.smallGraceTabNotes); 
-        o.set("extendBendArrowsOnTiedNotes", obj.extendBendArrowsOnTiedNotes); 
-        o.set("extendLineEffectsToBeatEnd", obj.extendLineEffectsToBeatEnd); 
-        o.set("slurHeight", obj.slurHeight); 
+        o.set("rhythmmode", obj.rhythmMode as number); 
+        o.set("rhythmheight", obj.rhythmHeight); 
+        o.set("transpositionpitches", obj.transpositionPitches); 
+        o.set("displaytranspositionpitches", obj.displayTranspositionPitches); 
+        o.set("smallgracetabnotes", obj.smallGraceTabNotes); 
+        o.set("extendbendarrowsontiednotes", obj.extendBendArrowsOnTiedNotes); 
+        o.set("extendlineeffectstobeatend", obj.extendLineEffectsToBeatEnd); 
+        o.set("slurheight", obj.slurHeight); 
         return o; 
     }
     public static setProperty(obj: NotationSettings, property: string, v: unknown): boolean {
diff --git a/src/generated/PlayerSettingsSerializer.ts b/src/generated/PlayerSettingsSerializer.ts
index 87944ee49..54d338ebe 100644
--- a/src/generated/PlayerSettingsSerializer.ts
+++ b/src/generated/PlayerSettingsSerializer.ts
@@ -20,24 +20,24 @@ export class PlayerSettingsSerializer {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        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); 
-        o.set("enableElementHighlighting", obj.enableElementHighlighting); 
-        o.set("enableUserInteraction", obj.enableUserInteraction); 
-        o.set("scrollOffsetX", obj.scrollOffsetX); 
-        o.set("scrollOffsetY", obj.scrollOffsetY); 
-        o.set("scrollMode", obj.scrollMode as number); 
-        o.set("scrollSpeed", obj.scrollSpeed); 
+        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); 
+        o.set("enableelementhighlighting", obj.enableElementHighlighting); 
+        o.set("enableuserinteraction", obj.enableUserInteraction); 
+        o.set("scrolloffsetx", obj.scrollOffsetX); 
+        o.set("scrolloffsety", obj.scrollOffsetY); 
+        o.set("scrollmode", obj.scrollMode as number); 
+        o.set("scrollspeed", obj.scrollSpeed); 
         /*@target web*/
-        o.set("nativeBrowserSmoothScroll", obj.nativeBrowserSmoothScroll); 
-        o.set("songBookBendDuration", obj.songBookBendDuration); 
-        o.set("songBookDipDuration", obj.songBookDipDuration); 
+        o.set("nativebrowsersmoothscroll", obj.nativeBrowserSmoothScroll); 
+        o.set("songbookbendduration", obj.songBookBendDuration); 
+        o.set("songbookdipduration", obj.songBookDipDuration); 
         o.set("vibrato", VibratoPlaybackSettingsSerializer.toJson(obj.vibrato)); 
         o.set("slide", SlidePlaybackSettingsSerializer.toJson(obj.slide)); 
-        o.set("playTripletFeel", obj.playTripletFeel); 
+        o.set("playtripletfeel", obj.playTripletFeel); 
         return o; 
     }
     public static setProperty(obj: PlayerSettings, property: string, v: unknown): boolean {
diff --git a/src/generated/RenderingResourcesSerializer.ts b/src/generated/RenderingResourcesSerializer.ts
index e7bb535d8..fb0bed86e 100644
--- a/src/generated/RenderingResourcesSerializer.ts
+++ b/src/generated/RenderingResourcesSerializer.ts
@@ -19,23 +19,23 @@ export class RenderingResourcesSerializer {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("copyrightFont", Font.toJson(obj.copyrightFont)); 
-        o.set("titleFont", Font.toJson(obj.titleFont)); 
-        o.set("subTitleFont", Font.toJson(obj.subTitleFont)); 
-        o.set("wordsFont", Font.toJson(obj.wordsFont)); 
-        o.set("effectFont", Font.toJson(obj.effectFont)); 
-        o.set("fretboardNumberFont", Font.toJson(obj.fretboardNumberFont)); 
-        o.set("tablatureFont", Font.toJson(obj.tablatureFont)); 
-        o.set("graceFont", Font.toJson(obj.graceFont)); 
-        o.set("staffLineColor", Color.toJson(obj.staffLineColor)); 
-        o.set("barSeparatorColor", Color.toJson(obj.barSeparatorColor)); 
-        o.set("barNumberFont", Font.toJson(obj.barNumberFont)); 
-        o.set("barNumberColor", Color.toJson(obj.barNumberColor)); 
-        o.set("fingeringFont", Font.toJson(obj.fingeringFont)); 
-        o.set("markerFont", Font.toJson(obj.markerFont)); 
-        o.set("mainGlyphColor", Color.toJson(obj.mainGlyphColor)); 
-        o.set("secondaryGlyphColor", Color.toJson(obj.secondaryGlyphColor)); 
-        o.set("scoreInfoColor", Color.toJson(obj.scoreInfoColor)); 
+        o.set("copyrightfont", Font.toJson(obj.copyrightFont)); 
+        o.set("titlefont", Font.toJson(obj.titleFont)); 
+        o.set("subtitlefont", Font.toJson(obj.subTitleFont)); 
+        o.set("wordsfont", Font.toJson(obj.wordsFont)); 
+        o.set("effectfont", Font.toJson(obj.effectFont)); 
+        o.set("fretboardnumberfont", Font.toJson(obj.fretboardNumberFont)); 
+        o.set("tablaturefont", Font.toJson(obj.tablatureFont)); 
+        o.set("gracefont", Font.toJson(obj.graceFont)); 
+        o.set("stafflinecolor", Color.toJson(obj.staffLineColor)); 
+        o.set("barseparatorcolor", Color.toJson(obj.barSeparatorColor)); 
+        o.set("barnumberfont", Font.toJson(obj.barNumberFont)); 
+        o.set("barnumbercolor", Color.toJson(obj.barNumberColor)); 
+        o.set("fingeringfont", Font.toJson(obj.fingeringFont)); 
+        o.set("markerfont", Font.toJson(obj.markerFont)); 
+        o.set("mainglyphcolor", Color.toJson(obj.mainGlyphColor)); 
+        o.set("secondaryglyphcolor", Color.toJson(obj.secondaryGlyphColor)); 
+        o.set("scoreinfocolor", Color.toJson(obj.scoreInfoColor)); 
         return o; 
     }
     public static setProperty(obj: RenderingResources, property: string, v: unknown): boolean {
diff --git a/src/generated/SlidePlaybackSettingsSerializer.ts b/src/generated/SlidePlaybackSettingsSerializer.ts
index 5c32ce414..d2cd24c86 100644
--- a/src/generated/SlidePlaybackSettingsSerializer.ts
+++ b/src/generated/SlidePlaybackSettingsSerializer.ts
@@ -17,9 +17,9 @@ export class SlidePlaybackSettingsSerializer {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("simpleSlidePitchOffset", obj.simpleSlidePitchOffset); 
-        o.set("simpleSlideDurationRatio", obj.simpleSlideDurationRatio); 
-        o.set("shiftSlideDurationRatio", obj.shiftSlideDurationRatio); 
+        o.set("simpleslidepitchoffset", obj.simpleSlidePitchOffset); 
+        o.set("simpleslidedurationratio", obj.simpleSlideDurationRatio); 
+        o.set("shiftslidedurationratio", obj.shiftSlideDurationRatio); 
         return o; 
     }
     public static setProperty(obj: SlidePlaybackSettings, property: string, v: unknown): boolean {
diff --git a/src/generated/VibratoPlaybackSettingsSerializer.ts b/src/generated/VibratoPlaybackSettingsSerializer.ts
index 092c24d4a..f623538f4 100644
--- a/src/generated/VibratoPlaybackSettingsSerializer.ts
+++ b/src/generated/VibratoPlaybackSettingsSerializer.ts
@@ -17,14 +17,14 @@ export class VibratoPlaybackSettingsSerializer {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("noteWideLength", obj.noteWideLength); 
-        o.set("noteWideAmplitude", obj.noteWideAmplitude); 
-        o.set("noteSlightLength", obj.noteSlightLength); 
-        o.set("noteSlightAmplitude", obj.noteSlightAmplitude); 
-        o.set("beatWideLength", obj.beatWideLength); 
-        o.set("beatWideAmplitude", obj.beatWideAmplitude); 
-        o.set("beatSlightLength", obj.beatSlightLength); 
-        o.set("beatSlightAmplitude", obj.beatSlightAmplitude); 
+        o.set("notewidelength", obj.noteWideLength); 
+        o.set("notewideamplitude", obj.noteWideAmplitude); 
+        o.set("noteslightlength", obj.noteSlightLength); 
+        o.set("noteslightamplitude", obj.noteSlightAmplitude); 
+        o.set("beatwidelength", obj.beatWideLength); 
+        o.set("beatwideamplitude", obj.beatWideAmplitude); 
+        o.set("beatslightlength", obj.beatSlightLength); 
+        o.set("beatslightamplitude", obj.beatSlightAmplitude); 
         return o; 
     }
     public static setProperty(obj: VibratoPlaybackSettings, property: string, v: unknown): boolean {
diff --git a/src/generated/model/AutomationSerializer.ts b/src/generated/model/AutomationSerializer.ts
index 46983de67..8c2f04fa9 100644
--- a/src/generated/model/AutomationSerializer.ts
+++ b/src/generated/model/AutomationSerializer.ts
@@ -11,17 +11,17 @@ export class AutomationSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Automation | null): Map<string, unknown> | null {
         if (!obj) {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("isLinear", obj.isLinear); 
+        o.set("islinear", obj.isLinear); 
         o.set("type", obj.type as number); 
         o.set("value", obj.value); 
-        o.set("ratioPosition", obj.ratioPosition); 
+        o.set("ratioposition", obj.ratioPosition); 
         o.set("text", obj.text); 
         return o; 
     }
diff --git a/src/generated/model/BarSerializer.ts b/src/generated/model/BarSerializer.ts
index 5773b2549..0f7a88fa5 100644
--- a/src/generated/model/BarSerializer.ts
+++ b/src/generated/model/BarSerializer.ts
@@ -15,7 +15,7 @@ export class BarSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Bar | null): Map<string, unknown> | null {
         if (!obj) {
@@ -24,9 +24,9 @@ export class BarSerializer {
         const o = new Map<string, unknown>(); 
         o.set("id", obj.id); 
         o.set("clef", obj.clef as number); 
-        o.set("clefOttava", obj.clefOttava as number); 
+        o.set("clefottava", obj.clefOttava as number); 
         o.set("voices", obj.voices.map(i => VoiceSerializer.toJson(i))); 
-        o.set("simileMark", obj.simileMark as number); 
+        o.set("similemark", obj.simileMark as number); 
         return o; 
     }
     public static setProperty(obj: Bar, property: string, v: unknown): boolean {
@@ -42,9 +42,9 @@ export class BarSerializer {
                 return true;
             case "voices":
                 obj.voices = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new Voice();
-                    VoiceSerializer.fromJson(i, o)
+                    VoiceSerializer.fromJson(i, o);
                     obj.addVoice(i);
                 }
                 return true;
diff --git a/src/generated/model/BeatCloner.ts b/src/generated/model/BeatCloner.ts
index f9396dbb8..e01487f59 100644
--- a/src/generated/model/BeatCloner.ts
+++ b/src/generated/model/BeatCloner.ts
@@ -12,7 +12,7 @@ export class BeatCloner {
         const clone = new Beat(); 
         clone.index = original.index; 
         clone.notes = []; 
-        for (const i of original.notes) {
+        for (const i of original.notes!) {
             clone.addNote(NoteCloner.clone(i));
         } 
         clone.isEmpty = original.isEmpty; 
@@ -23,7 +23,7 @@ export class BeatCloner {
         clone.isLetRing = original.isLetRing; 
         clone.isPalmMute = original.isPalmMute; 
         clone.automations = []; 
-        for (const i of original.automations) {
+        for (const i of original.automations!) {
             clone.automations.push(AutomationCloner.clone(i));
         } 
         clone.dots = original.dots; 
@@ -40,9 +40,11 @@ export class BeatCloner {
         clone.tupletNumerator = original.tupletNumerator; 
         clone.isContinuedWhammy = original.isContinuedWhammy; 
         clone.whammyBarType = original.whammyBarType; 
-        clone.whammyBarPoints = []; 
-        for (const i of original.whammyBarPoints) {
-            clone.addWhammyBarPoint(BendPointCloner.clone(i));
+        if (original.whammyBarPoints) {
+            clone.whammyBarPoints = [];
+            for (const i of original.whammyBarPoints!) {
+                clone.addWhammyBarPoint(BendPointCloner.clone(i));
+            }
         } 
         clone.vibrato = original.vibrato; 
         clone.chordId = original.chordId; 
diff --git a/src/generated/model/BeatSerializer.ts b/src/generated/model/BeatSerializer.ts
index 1c31d658f..468a090ad 100644
--- a/src/generated/model/BeatSerializer.ts
+++ b/src/generated/model/BeatSerializer.ts
@@ -28,7 +28,7 @@ export class BeatSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Beat | null): Map<string, unknown> | null {
         if (!obj) {
@@ -37,41 +37,43 @@ export class BeatSerializer {
         const o = new Map<string, unknown>(); 
         o.set("id", obj.id); 
         o.set("notes", obj.notes.map(i => NoteSerializer.toJson(i))); 
-        o.set("isEmpty", obj.isEmpty); 
-        o.set("whammyStyle", obj.whammyStyle as number); 
+        o.set("isempty", obj.isEmpty); 
+        o.set("whammystyle", obj.whammyStyle as number); 
         o.set("ottava", obj.ottava as number); 
-        o.set("isLegatoOrigin", obj.isLegatoOrigin); 
+        o.set("islegatoorigin", obj.isLegatoOrigin); 
         o.set("duration", obj.duration as number); 
         o.set("automations", obj.automations.map(i => AutomationSerializer.toJson(i))); 
         o.set("dots", obj.dots); 
-        o.set("fadeIn", obj.fadeIn); 
+        o.set("fadein", obj.fadeIn); 
         o.set("lyrics", obj.lyrics); 
-        o.set("hasRasgueado", obj.hasRasgueado); 
+        o.set("hasrasgueado", obj.hasRasgueado); 
         o.set("pop", obj.pop); 
         o.set("slap", obj.slap); 
         o.set("tap", obj.tap); 
         o.set("text", obj.text); 
-        o.set("brushType", obj.brushType as number); 
-        o.set("brushDuration", obj.brushDuration); 
-        o.set("tupletDenominator", obj.tupletDenominator); 
-        o.set("tupletNumerator", obj.tupletNumerator); 
-        o.set("isContinuedWhammy", obj.isContinuedWhammy); 
-        o.set("whammyBarType", obj.whammyBarType as number); 
-        o.set("whammyBarPoints", obj.whammyBarPoints.map(i => BendPointSerializer.toJson(i))); 
+        o.set("brushtype", obj.brushType as number); 
+        o.set("brushduration", obj.brushDuration); 
+        o.set("tupletdenominator", obj.tupletDenominator); 
+        o.set("tupletnumerator", obj.tupletNumerator); 
+        o.set("iscontinuedwhammy", obj.isContinuedWhammy); 
+        o.set("whammybartype", obj.whammyBarType as number); 
+        if (obj.whammyBarPoints !== null) {
+            o.set("whammybarpoints", obj.whammyBarPoints?.map(i => BendPointSerializer.toJson(i)));
+        } 
         o.set("vibrato", obj.vibrato as number); 
-        o.set("chordId", obj.chordId); 
-        o.set("graceType", obj.graceType as number); 
-        o.set("pickStroke", obj.pickStroke as number); 
-        o.set("tremoloSpeed", obj.tremoloSpeed as number | null); 
+        o.set("chordid", obj.chordId); 
+        o.set("gracetype", obj.graceType as number); 
+        o.set("pickstroke", obj.pickStroke as number); 
+        o.set("tremolospeed", obj.tremoloSpeed as number | null); 
         o.set("crescendo", obj.crescendo as number); 
-        o.set("displayStart", obj.displayStart); 
-        o.set("playbackStart", obj.playbackStart); 
-        o.set("displayDuration", obj.displayDuration); 
-        o.set("playbackDuration", obj.playbackDuration); 
+        o.set("displaystart", obj.displayStart); 
+        o.set("playbackstart", obj.playbackStart); 
+        o.set("displayduration", obj.displayDuration); 
+        o.set("playbackduration", obj.playbackDuration); 
         o.set("dynamics", obj.dynamics as number); 
-        o.set("invertBeamDirection", obj.invertBeamDirection); 
-        o.set("preferredBeamDirection", obj.preferredBeamDirection as number | null); 
-        o.set("beamingMode", obj.beamingMode as number); 
+        o.set("invertbeamdirection", obj.invertBeamDirection); 
+        o.set("preferredbeamdirection", obj.preferredBeamDirection as number | null); 
+        o.set("beamingmode", obj.beamingMode as number); 
         return o; 
     }
     public static setProperty(obj: Beat, property: string, v: unknown): boolean {
@@ -81,9 +83,9 @@ export class BeatSerializer {
                 return true;
             case "notes":
                 obj.notes = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new Note();
-                    NoteSerializer.fromJson(i, o)
+                    NoteSerializer.fromJson(i, o);
                     obj.addNote(i);
                 }
                 return true;
@@ -104,9 +106,9 @@ export class BeatSerializer {
                 return true;
             case "automations":
                 obj.automations = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new Automation();
-                    AutomationSerializer.fromJson(i, o)
+                    AutomationSerializer.fromJson(i, o);
                     obj.automations.push(i);
                 }
                 return true;
@@ -153,11 +155,13 @@ export class BeatSerializer {
                 obj.whammyBarType = JsonHelper.parseEnum<WhammyType>(v, WhammyType)!;
                 return true;
             case "whammybarpoints":
-                obj.whammyBarPoints = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
-                    const i = new BendPoint();
-                    BendPointSerializer.fromJson(i, o)
-                    obj.addWhammyBarPoint(i);
+                if (v) {
+                    obj.whammyBarPoints = [];
+                    for (const o of (v as (Map<string, unknown> | null)[])) {
+                        const i = new BendPoint();
+                        BendPointSerializer.fromJson(i, o);
+                        obj.addWhammyBarPoint(i);
+                    }
                 }
                 return true;
             case "vibrato":
diff --git a/src/generated/model/BendPointSerializer.ts b/src/generated/model/BendPointSerializer.ts
index cb27f39d3..4d08a2af4 100644
--- a/src/generated/model/BendPointSerializer.ts
+++ b/src/generated/model/BendPointSerializer.ts
@@ -10,7 +10,7 @@ export class BendPointSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: BendPoint | null): Map<string, unknown> | null {
         if (!obj) {
diff --git a/src/generated/model/ChordSerializer.ts b/src/generated/model/ChordSerializer.ts
index bfce0f51b..39da50893 100644
--- a/src/generated/model/ChordSerializer.ts
+++ b/src/generated/model/ChordSerializer.ts
@@ -10,7 +10,7 @@ export class ChordSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Chord | null): Map<string, unknown> | null {
         if (!obj) {
@@ -18,12 +18,12 @@ export class ChordSerializer {
         } 
         const o = new Map<string, unknown>(); 
         o.set("name", obj.name); 
-        o.set("firstFret", obj.firstFret); 
+        o.set("firstfret", obj.firstFret); 
         o.set("strings", obj.strings); 
-        o.set("barreFrets", obj.barreFrets); 
-        o.set("showName", obj.showName); 
-        o.set("showDiagram", obj.showDiagram); 
-        o.set("showFingering", obj.showFingering); 
+        o.set("barrefrets", obj.barreFrets); 
+        o.set("showname", obj.showName); 
+        o.set("showdiagram", obj.showDiagram); 
+        o.set("showfingering", obj.showFingering); 
         return o; 
     }
     public static setProperty(obj: Chord, property: string, v: unknown): boolean {
diff --git a/src/generated/model/FermataSerializer.ts b/src/generated/model/FermataSerializer.ts
index e4335acb6..904824fec 100644
--- a/src/generated/model/FermataSerializer.ts
+++ b/src/generated/model/FermataSerializer.ts
@@ -11,7 +11,7 @@ export class FermataSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Fermata | null): Map<string, unknown> | null {
         if (!obj) {
diff --git a/src/generated/model/InstrumentArticulationSerializer.ts b/src/generated/model/InstrumentArticulationSerializer.ts
index 8aa3863c2..f0d67f047 100644
--- a/src/generated/model/InstrumentArticulationSerializer.ts
+++ b/src/generated/model/InstrumentArticulationSerializer.ts
@@ -12,21 +12,21 @@ export class InstrumentArticulationSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: InstrumentArticulation | null): Map<string, unknown> | null {
         if (!obj) {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("elementType", obj.elementType); 
-        o.set("staffLine", obj.staffLine); 
-        o.set("noteHeadDefault", obj.noteHeadDefault as number); 
-        o.set("noteHeadHalf", obj.noteHeadHalf as number); 
-        o.set("noteHeadWhole", obj.noteHeadWhole as number); 
-        o.set("techniqueSymbol", obj.techniqueSymbol as number); 
-        o.set("techniqueSymbolPlacement", obj.techniqueSymbolPlacement as number); 
-        o.set("outputMidiNumber", obj.outputMidiNumber); 
+        o.set("elementtype", obj.elementType); 
+        o.set("staffline", obj.staffLine); 
+        o.set("noteheaddefault", obj.noteHeadDefault as number); 
+        o.set("noteheadhalf", obj.noteHeadHalf as number); 
+        o.set("noteheadwhole", obj.noteHeadWhole as number); 
+        o.set("techniquesymbol", obj.techniqueSymbol as number); 
+        o.set("techniquesymbolplacement", obj.techniqueSymbolPlacement as number); 
+        o.set("outputmidinumber", obj.outputMidiNumber); 
         return o; 
     }
     public static setProperty(obj: InstrumentArticulation, property: string, v: unknown): boolean {
diff --git a/src/generated/model/MasterBarSerializer.ts b/src/generated/model/MasterBarSerializer.ts
index c93a8f894..92490a0c1 100644
--- a/src/generated/model/MasterBarSerializer.ts
+++ b/src/generated/model/MasterBarSerializer.ts
@@ -19,34 +19,34 @@ export class MasterBarSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: MasterBar | null): Map<string, unknown> | null {
         if (!obj) {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("alternateEndings", obj.alternateEndings); 
-        o.set("keySignature", obj.keySignature as number); 
-        o.set("keySignatureType", obj.keySignatureType as number); 
-        o.set("isDoubleBar", obj.isDoubleBar); 
-        o.set("isRepeatStart", obj.isRepeatStart); 
-        o.set("repeatCount", obj.repeatCount); 
-        o.set("timeSignatureNumerator", obj.timeSignatureNumerator); 
-        o.set("timeSignatureDenominator", obj.timeSignatureDenominator); 
-        o.set("timeSignatureCommon", obj.timeSignatureCommon); 
-        o.set("tripletFeel", obj.tripletFeel as number); 
+        o.set("alternateendings", obj.alternateEndings); 
+        o.set("keysignature", obj.keySignature as number); 
+        o.set("keysignaturetype", obj.keySignatureType as number); 
+        o.set("isdoublebar", obj.isDoubleBar); 
+        o.set("isrepeatstart", obj.isRepeatStart); 
+        o.set("repeatcount", obj.repeatCount); 
+        o.set("timesignaturenumerator", obj.timeSignatureNumerator); 
+        o.set("timesignaturedenominator", obj.timeSignatureDenominator); 
+        o.set("timesignaturecommon", obj.timeSignatureCommon); 
+        o.set("tripletfeel", obj.tripletFeel as number); 
         o.set("section", SectionSerializer.toJson(obj.section)); 
-        o.set("tempoAutomation", AutomationSerializer.toJson(obj.tempoAutomation)); 
-        {
+        o.set("tempoautomation", AutomationSerializer.toJson(obj.tempoAutomation)); 
+        if (obj.fermata !== null) {
             const m = new Map<string, unknown>();
             o.set("fermata", m);
-            for (const [k, v] of obj.fermata) {
+            for (const [k, v] of obj.fermata!) {
                 m.set(k.toString(), FermataSerializer.toJson(v));
             }
         } 
         o.set("start", obj.start); 
-        o.set("isAnacrusis", obj.isAnacrusis); 
+        o.set("isanacrusis", obj.isAnacrusis); 
         return o; 
     }
     public static setProperty(obj: MasterBar, property: string, v: unknown): boolean {
@@ -86,7 +86,7 @@ export class MasterBarSerializer {
                 JsonHelper.forEach(v, (v, k) => {
                     const i = new Fermata(); 
                     FermataSerializer.fromJson(i, v as Map<string, unknown>); 
-                    obj.fermata.set(parseInt(k), i); 
+                    obj.addFermata(parseInt(k), i); 
                 });
                 return true;
             case "start":
diff --git a/src/generated/model/NoteCloner.ts b/src/generated/model/NoteCloner.ts
index 8cc7eb248..889383993 100644
--- a/src/generated/model/NoteCloner.ts
+++ b/src/generated/model/NoteCloner.ts
@@ -13,9 +13,11 @@ export class NoteCloner {
         clone.bendType = original.bendType; 
         clone.bendStyle = original.bendStyle; 
         clone.isContinuedBend = original.isContinuedBend; 
-        clone.bendPoints = []; 
-        for (const i of original.bendPoints) {
-            clone.addBendPoint(BendPointCloner.clone(i));
+        if (original.bendPoints) {
+            clone.bendPoints = [];
+            for (const i of original.bendPoints!) {
+                clone.addBendPoint(BendPointCloner.clone(i));
+            }
         } 
         clone.fret = original.fret; 
         clone.string = original.string; 
@@ -25,11 +27,7 @@ export class NoteCloner {
         clone.isVisible = original.isVisible; 
         clone.isLeftHandTapped = original.isLeftHandTapped; 
         clone.isHammerPullOrigin = original.isHammerPullOrigin; 
-        clone.hammerPullOriginNoteId = original.hammerPullOriginNoteId; 
-        clone.hammerPullDestinationNoteId = original.hammerPullDestinationNoteId; 
         clone.isSlurDestination = original.isSlurDestination; 
-        clone.slurOriginNoteId = original.slurOriginNoteId; 
-        clone.slurDestinationNoteId = original.slurDestinationNoteId; 
         clone.harmonicType = original.harmonicType; 
         clone.harmonicValue = original.harmonicValue; 
         clone.isGhost = original.isGhost; 
@@ -40,8 +38,6 @@ export class NoteCloner {
         clone.slideInType = original.slideInType; 
         clone.slideOutType = original.slideOutType; 
         clone.vibrato = original.vibrato; 
-        clone.tieOriginNoteId = original.tieOriginNoteId; 
-        clone.tieDestinationNoteId = original.tieDestinationNoteId; 
         clone.isTieDestination = original.isTieDestination; 
         clone.leftHandFinger = original.leftHandFinger; 
         clone.rightHandFinger = original.rightHandFinger; 
diff --git a/src/generated/model/NoteSerializer.ts b/src/generated/model/NoteSerializer.ts
index 1e47ffbaf..b5b8f2c6e 100644
--- a/src/generated/model/NoteSerializer.ts
+++ b/src/generated/model/NoteSerializer.ts
@@ -23,7 +23,7 @@ export class NoteSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Note | null): Map<string, unknown> | null {
         if (!obj) {
@@ -32,44 +32,41 @@ export class NoteSerializer {
         const o = new Map<string, unknown>(); 
         o.set("id", obj.id); 
         o.set("accentuated", obj.accentuated as number); 
-        o.set("bendType", obj.bendType as number); 
-        o.set("bendStyle", obj.bendStyle as number); 
-        o.set("isContinuedBend", obj.isContinuedBend); 
-        o.set("bendPoints", obj.bendPoints.map(i => BendPointSerializer.toJson(i))); 
+        o.set("bendtype", obj.bendType as number); 
+        o.set("bendstyle", obj.bendStyle as number); 
+        o.set("iscontinuedbend", obj.isContinuedBend); 
+        if (obj.bendPoints !== null) {
+            o.set("bendpoints", obj.bendPoints?.map(i => BendPointSerializer.toJson(i)));
+        } 
         o.set("fret", obj.fret); 
         o.set("string", obj.string); 
         o.set("octave", obj.octave); 
         o.set("tone", obj.tone); 
-        o.set("percussionArticulation", obj.percussionArticulation); 
-        o.set("isVisible", obj.isVisible); 
-        o.set("isLeftHandTapped", obj.isLeftHandTapped); 
-        o.set("isHammerPullOrigin", obj.isHammerPullOrigin); 
-        o.set("hammerPullOriginNoteId", obj.hammerPullOriginNoteId); 
-        o.set("hammerPullDestinationNoteId", obj.hammerPullDestinationNoteId); 
-        o.set("isSlurDestination", obj.isSlurDestination); 
-        o.set("slurOriginNoteId", obj.slurOriginNoteId); 
-        o.set("slurDestinationNoteId", obj.slurDestinationNoteId); 
-        o.set("harmonicType", obj.harmonicType as number); 
-        o.set("harmonicValue", obj.harmonicValue); 
-        o.set("isGhost", obj.isGhost); 
-        o.set("isLetRing", obj.isLetRing); 
-        o.set("isPalmMute", obj.isPalmMute); 
-        o.set("isDead", obj.isDead); 
-        o.set("isStaccato", obj.isStaccato); 
-        o.set("slideInType", obj.slideInType as number); 
-        o.set("slideOutType", obj.slideOutType as number); 
+        o.set("percussionarticulation", obj.percussionArticulation); 
+        o.set("isvisible", obj.isVisible); 
+        o.set("islefthandtapped", obj.isLeftHandTapped); 
+        o.set("ishammerpullorigin", obj.isHammerPullOrigin); 
+        o.set("isslurdestination", obj.isSlurDestination); 
+        o.set("harmonictype", obj.harmonicType as number); 
+        o.set("harmonicvalue", obj.harmonicValue); 
+        o.set("isghost", obj.isGhost); 
+        o.set("isletring", obj.isLetRing); 
+        o.set("ispalmmute", obj.isPalmMute); 
+        o.set("isdead", obj.isDead); 
+        o.set("isstaccato", obj.isStaccato); 
+        o.set("slideintype", obj.slideInType as number); 
+        o.set("slideouttype", obj.slideOutType as number); 
         o.set("vibrato", obj.vibrato as number); 
-        o.set("tieOriginNoteId", obj.tieOriginNoteId); 
-        o.set("tieDestinationNoteId", obj.tieDestinationNoteId); 
-        o.set("isTieDestination", obj.isTieDestination); 
-        o.set("leftHandFinger", obj.leftHandFinger as number); 
-        o.set("rightHandFinger", obj.rightHandFinger as number); 
-        o.set("isFingering", obj.isFingering); 
-        o.set("trillValue", obj.trillValue); 
-        o.set("trillSpeed", obj.trillSpeed as number); 
-        o.set("durationPercent", obj.durationPercent); 
-        o.set("accidentalMode", obj.accidentalMode as number); 
+        o.set("istiedestination", obj.isTieDestination); 
+        o.set("lefthandfinger", obj.leftHandFinger as number); 
+        o.set("righthandfinger", obj.rightHandFinger as number); 
+        o.set("isfingering", obj.isFingering); 
+        o.set("trillvalue", obj.trillValue); 
+        o.set("trillspeed", obj.trillSpeed as number); 
+        o.set("durationpercent", obj.durationPercent); 
+        o.set("accidentalmode", obj.accidentalMode as number); 
         o.set("dynamics", obj.dynamics as number); 
+        obj.toJson(o); 
         return o; 
     }
     public static setProperty(obj: Note, property: string, v: unknown): boolean {
@@ -90,11 +87,13 @@ export class NoteSerializer {
                 obj.isContinuedBend = v! as boolean;
                 return true;
             case "bendpoints":
-                obj.bendPoints = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
-                    const i = new BendPoint();
-                    BendPointSerializer.fromJson(i, o)
-                    obj.addBendPoint(i);
+                if (v) {
+                    obj.bendPoints = [];
+                    for (const o of (v as (Map<string, unknown> | null)[])) {
+                        const i = new BendPoint();
+                        BendPointSerializer.fromJson(i, o);
+                        obj.addBendPoint(i);
+                    }
                 }
                 return true;
             case "fret":
@@ -121,21 +120,9 @@ export class NoteSerializer {
             case "ishammerpullorigin":
                 obj.isHammerPullOrigin = v! as boolean;
                 return true;
-            case "hammerpulloriginnoteid":
-                obj.hammerPullOriginNoteId = v! as number;
-                return true;
-            case "hammerpulldestinationnoteid":
-                obj.hammerPullDestinationNoteId = v! as number;
-                return true;
             case "isslurdestination":
                 obj.isSlurDestination = v! as boolean;
                 return true;
-            case "sluroriginnoteid":
-                obj.slurOriginNoteId = v! as number;
-                return true;
-            case "slurdestinationnoteid":
-                obj.slurDestinationNoteId = v! as number;
-                return true;
             case "harmonictype":
                 obj.harmonicType = JsonHelper.parseEnum<HarmonicType>(v, HarmonicType)!;
                 return true;
@@ -166,12 +153,6 @@ export class NoteSerializer {
             case "vibrato":
                 obj.vibrato = JsonHelper.parseEnum<VibratoType>(v, VibratoType)!;
                 return true;
-            case "tieoriginnoteid":
-                obj.tieOriginNoteId = v! as number;
-                return true;
-            case "tiedestinationnoteid":
-                obj.tieDestinationNoteId = v! as number;
-                return true;
             case "istiedestination":
                 obj.isTieDestination = v! as boolean;
                 return true;
@@ -200,7 +181,7 @@ export class NoteSerializer {
                 obj.dynamics = JsonHelper.parseEnum<DynamicValue>(v, DynamicValue)!;
                 return true;
         } 
-        return false; 
+        return obj.setProperty(property, v); 
     }
 }
 
diff --git a/src/generated/model/PlaybackInformationSerializer.ts b/src/generated/model/PlaybackInformationSerializer.ts
index c80226b3b..62f9ab5cc 100644
--- a/src/generated/model/PlaybackInformationSerializer.ts
+++ b/src/generated/model/PlaybackInformationSerializer.ts
@@ -10,7 +10,7 @@ export class PlaybackInformationSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: PlaybackInformation | null): Map<string, unknown> | null {
         if (!obj) {
@@ -21,10 +21,10 @@ export class PlaybackInformationSerializer {
         o.set("balance", obj.balance); 
         o.set("port", obj.port); 
         o.set("program", obj.program); 
-        o.set("primaryChannel", obj.primaryChannel); 
-        o.set("secondaryChannel", obj.secondaryChannel); 
-        o.set("isMute", obj.isMute); 
-        o.set("isSolo", obj.isSolo); 
+        o.set("primarychannel", obj.primaryChannel); 
+        o.set("secondarychannel", obj.secondaryChannel); 
+        o.set("ismute", obj.isMute); 
+        o.set("issolo", obj.isSolo); 
         return o; 
     }
     public static setProperty(obj: PlaybackInformation, property: string, v: unknown): boolean {
diff --git a/src/generated/model/RenderStylesheetSerializer.ts b/src/generated/model/RenderStylesheetSerializer.ts
index c233a68e8..2132b1829 100644
--- a/src/generated/model/RenderStylesheetSerializer.ts
+++ b/src/generated/model/RenderStylesheetSerializer.ts
@@ -10,14 +10,14 @@ export class RenderStylesheetSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: RenderStylesheet | null): Map<string, unknown> | null {
         if (!obj) {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("hideDynamics", obj.hideDynamics); 
+        o.set("hidedynamics", obj.hideDynamics); 
         return o; 
     }
     public static setProperty(obj: RenderStylesheet, property: string, v: unknown): boolean {
diff --git a/src/generated/model/ScoreSerializer.ts b/src/generated/model/ScoreSerializer.ts
index ee67ddaf0..df30c54a5 100644
--- a/src/generated/model/ScoreSerializer.ts
+++ b/src/generated/model/ScoreSerializer.ts
@@ -15,7 +15,7 @@ export class ScoreSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Score | null): Map<string, unknown> | null {
         if (!obj) {
@@ -28,13 +28,13 @@ export class ScoreSerializer {
         o.set("instructions", obj.instructions); 
         o.set("music", obj.music); 
         o.set("notices", obj.notices); 
-        o.set("subTitle", obj.subTitle); 
+        o.set("subtitle", obj.subTitle); 
         o.set("title", obj.title); 
         o.set("words", obj.words); 
         o.set("tab", obj.tab); 
         o.set("tempo", obj.tempo); 
-        o.set("tempoLabel", obj.tempoLabel); 
-        o.set("masterBars", obj.masterBars.map(i => MasterBarSerializer.toJson(i))); 
+        o.set("tempolabel", obj.tempoLabel); 
+        o.set("masterbars", obj.masterBars.map(i => MasterBarSerializer.toJson(i))); 
         o.set("tracks", obj.tracks.map(i => TrackSerializer.toJson(i))); 
         o.set("stylesheet", RenderStylesheetSerializer.toJson(obj.stylesheet)); 
         return o; 
@@ -79,17 +79,17 @@ export class ScoreSerializer {
                 return true;
             case "masterbars":
                 obj.masterBars = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new MasterBar();
-                    MasterBarSerializer.fromJson(i, o)
+                    MasterBarSerializer.fromJson(i, o);
                     obj.addMasterBar(i);
                 }
                 return true;
             case "tracks":
                 obj.tracks = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new Track();
-                    TrackSerializer.fromJson(i, o)
+                    TrackSerializer.fromJson(i, o);
                     obj.addTrack(i);
                 }
                 return true;
diff --git a/src/generated/model/SectionSerializer.ts b/src/generated/model/SectionSerializer.ts
index 789025096..5ccd55603 100644
--- a/src/generated/model/SectionSerializer.ts
+++ b/src/generated/model/SectionSerializer.ts
@@ -10,7 +10,7 @@ export class SectionSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Section | null): Map<string, unknown> | null {
         if (!obj) {
diff --git a/src/generated/model/StaffSerializer.ts b/src/generated/model/StaffSerializer.ts
index d3ccfbb53..40a3280b0 100644
--- a/src/generated/model/StaffSerializer.ts
+++ b/src/generated/model/StaffSerializer.ts
@@ -15,7 +15,7 @@ export class StaffSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Staff | null): Map<string, unknown> | null {
         if (!obj) {
@@ -23,30 +23,30 @@ export class StaffSerializer {
         } 
         const o = new Map<string, unknown>(); 
         o.set("bars", obj.bars.map(i => BarSerializer.toJson(i))); 
-        {
+        if (obj.chords !== null) {
             const m = new Map<string, unknown>();
             o.set("chords", m);
-            for (const [k, v] of obj.chords) {
+            for (const [k, v] of obj.chords!) {
                 m.set(k.toString(), ChordSerializer.toJson(v));
             }
         } 
         o.set("capo", obj.capo); 
-        o.set("transpositionPitch", obj.transpositionPitch); 
-        o.set("displayTranspositionPitch", obj.displayTranspositionPitch); 
-        o.set("stringTuning", TuningSerializer.toJson(obj.stringTuning)); 
-        o.set("showTablature", obj.showTablature); 
-        o.set("showStandardNotation", obj.showStandardNotation); 
-        o.set("isPercussion", obj.isPercussion); 
-        o.set("standardNotationLineCount", obj.standardNotationLineCount); 
+        o.set("transpositionpitch", obj.transpositionPitch); 
+        o.set("displaytranspositionpitch", obj.displayTranspositionPitch); 
+        o.set("stringtuning", TuningSerializer.toJson(obj.stringTuning)); 
+        o.set("showtablature", obj.showTablature); 
+        o.set("showstandardnotation", obj.showStandardNotation); 
+        o.set("ispercussion", obj.isPercussion); 
+        o.set("standardnotationlinecount", obj.standardNotationLineCount); 
         return o; 
     }
     public static setProperty(obj: Staff, property: string, v: unknown): boolean {
         switch (property) {
             case "bars":
                 obj.bars = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new Bar();
-                    BarSerializer.fromJson(i, o)
+                    BarSerializer.fromJson(i, o);
                     obj.addBar(i);
                 }
                 return true;
diff --git a/src/generated/model/TrackSerializer.ts b/src/generated/model/TrackSerializer.ts
index eaeadd9e2..b4e820398 100644
--- a/src/generated/model/TrackSerializer.ts
+++ b/src/generated/model/TrackSerializer.ts
@@ -16,7 +16,7 @@ export class TrackSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Track | null): Map<string, unknown> | null {
         if (!obj) {
@@ -24,20 +24,20 @@ export class TrackSerializer {
         } 
         const o = new Map<string, unknown>(); 
         o.set("staves", obj.staves.map(i => StaffSerializer.toJson(i))); 
-        o.set("playbackInfo", PlaybackInformationSerializer.toJson(obj.playbackInfo)); 
+        o.set("playbackinfo", PlaybackInformationSerializer.toJson(obj.playbackInfo)); 
         o.set("color", Color.toJson(obj.color)); 
         o.set("name", obj.name); 
-        o.set("shortName", obj.shortName); 
-        o.set("percussionArticulations", obj.percussionArticulations.map(i => InstrumentArticulationSerializer.toJson(i))); 
+        o.set("shortname", obj.shortName); 
+        o.set("percussionarticulations", obj.percussionArticulations.map(i => InstrumentArticulationSerializer.toJson(i))); 
         return o; 
     }
     public static setProperty(obj: Track, property: string, v: unknown): boolean {
         switch (property) {
             case "staves":
                 obj.staves = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new Staff();
-                    StaffSerializer.fromJson(i, o)
+                    StaffSerializer.fromJson(i, o);
                     obj.addStaff(i);
                 }
                 return true;
@@ -52,9 +52,9 @@ export class TrackSerializer {
                 return true;
             case "percussionarticulations":
                 obj.percussionArticulations = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new InstrumentArticulation();
-                    InstrumentArticulationSerializer.fromJson(i, o)
+                    InstrumentArticulationSerializer.fromJson(i, o);
                     obj.percussionArticulations.push(i);
                 }
                 return true;
diff --git a/src/generated/model/TuningSerializer.ts b/src/generated/model/TuningSerializer.ts
index 8293eb823..5fe512dc2 100644
--- a/src/generated/model/TuningSerializer.ts
+++ b/src/generated/model/TuningSerializer.ts
@@ -10,14 +10,14 @@ export class TuningSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Tuning | null): Map<string, unknown> | null {
         if (!obj) {
             return null;
         } 
         const o = new Map<string, unknown>(); 
-        o.set("isStandard", obj.isStandard); 
+        o.set("isstandard", obj.isStandard); 
         o.set("name", obj.name); 
         o.set("tunings", obj.tunings); 
         return o; 
diff --git a/src/generated/model/VoiceSerializer.ts b/src/generated/model/VoiceSerializer.ts
index df0ec2fca..51845b884 100644
--- a/src/generated/model/VoiceSerializer.ts
+++ b/src/generated/model/VoiceSerializer.ts
@@ -12,7 +12,7 @@ export class VoiceSerializer {
         if (!m) {
             return;
         } 
-        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k.toLowerCase(), v)); 
+        JsonHelper.forEach(m, (v, k) => this.setProperty(obj, k, v)); 
     }
     public static toJson(obj: Voice | null): Map<string, unknown> | null {
         if (!obj) {
@@ -21,7 +21,7 @@ export class VoiceSerializer {
         const o = new Map<string, unknown>(); 
         o.set("id", obj.id); 
         o.set("beats", obj.beats.map(i => BeatSerializer.toJson(i))); 
-        o.set("isEmpty", obj.isEmpty); 
+        o.set("isempty", obj.isEmpty); 
         return o; 
     }
     public static setProperty(obj: Voice, property: string, v: unknown): boolean {
@@ -31,9 +31,9 @@ export class VoiceSerializer {
                 return true;
             case "beats":
                 obj.beats = [];
-                for (const o of v as (Map<string, unknown> | null)[]) {
+                for (const o of (v as (Map<string, unknown> | null)[])) {
                     const i = new Beat();
-                    BeatSerializer.fromJson(i, o)
+                    BeatSerializer.fromJson(i, o);
                     obj.addBeat(i);
                 }
                 return true;
diff --git a/src/importer/AlphaTexImporter.ts b/src/importer/AlphaTexImporter.ts
index b184045b3..93db6d11c 100644
--- a/src/importer/AlphaTexImporter.ts
+++ b/src/importer/AlphaTexImporter.ts
@@ -66,7 +66,7 @@ export class AlphaTexError extends AlphaTabError {
     public symbol: AlphaTexSymbols = AlphaTexSymbols.No;
     public symbolData: unknown = null;
 
-    public constructor(message: string) {
+    public constructor(message: string | null) {
         super(AlphaTabErrorType.AlphaTex, message);
         Object.setPrototypeOf(this, AlphaTexError.prototype);
     }
@@ -125,7 +125,7 @@ export class AlphaTexImporter extends ScoreImporter {
     private _staffHasExplicitTuning: boolean = false;
     private _staffTuningApplied: boolean = false;
 
-    public logErrors:boolean = false;
+    public logErrors: boolean = false;
 
     public constructor() {
         super();
@@ -140,11 +140,11 @@ export class AlphaTexImporter extends ScoreImporter {
         this._input = tex;
         this.settings = settings;
     }
-    
+
 
     public readScore(): Score {
         try {
-            if(this.data.length > 0) {
+            if (this.data.length > 0) {
                 this._input = IOHelper.toString(this.data.readAll(), this.settings.importer.encoding);
             }
             this._allowTuning = true;
@@ -199,7 +199,7 @@ export class AlphaTexImporter extends ScoreImporter {
         } else {
             e = AlphaTexError.symbolError(this._curChPos, nonterm, expected, expected, this._syData);
         }
-        if(this.logErrors) {
+        if (this.logErrors) {
             Logger.error(this.name, e.message!);
         }
         throw e;
@@ -207,7 +207,7 @@ export class AlphaTexImporter extends ScoreImporter {
 
     private errorMessage(message: string): void {
         let e: AlphaTexError = AlphaTexError.errorMessage(this._curChPos, message);
-        if(this.logErrors) {
+        if (this.logErrors) {
             Logger.error(this.name, e.message!);
         }
         throw e;
@@ -737,7 +737,7 @@ export class AlphaTexImporter extends ScoreImporter {
                         this.error('tuning', AlphaTexSymbols.Tuning, true);
                         break;
                 }
-                if (strings !== this._currentStaff.tuning.length && this._currentStaff.chords.size > 0) {
+                if (strings !== this._currentStaff.tuning.length && (this._currentStaff.chords?.size ?? 0) > 0) {
                     this.errorMessage('Tuning must be defined before any chord');
                 }
                 return true;
@@ -1286,22 +1286,24 @@ export class AlphaTexImporter extends ScoreImporter {
                 beat.addWhammyBarPoint(new BendPoint(offset, value));
                 this._sy = this.newSy();
             }
-            while (beat.whammyBarPoints.length > 60) {
-                beat.removeWhammyBarPoint(beat.whammyBarPoints.length - 1);
-            }
-            // set positions
-            if (!exact) {
-                let count: number = beat.whammyBarPoints.length;
-                let step: number = (60 / count) | 0;
-                let i: number = 0;
-                while (i < count) {
-                    beat.whammyBarPoints[i].offset = Math.min(60, i * step);
-                    i++;
+            if (beat.whammyBarPoints != null) {
+                while (beat.whammyBarPoints.length > 60) {
+                    beat.removeWhammyBarPoint(beat.whammyBarPoints.length - 1);
+                }
+                // set positions
+                if (!exact) {
+                    let count: number = beat.whammyBarPoints.length;
+                    let step: number = (60 / count) | 0;
+                    let i: number = 0;
+                    while (i < count) {
+                        beat.whammyBarPoints[i].offset = Math.min(60, i * step);
+                        i++;
+                    }
+                } else {
+                    beat.whammyBarPoints.sort((a, b) => {
+                        return a.offset - b.offset;
+                    });
                 }
-            } else {
-                beat.whammyBarPoints.sort((a, b) => {
-                    return a.offset - b.offset;
-                });
             }
             this._allowNegatives = false;
             if (this._sy !== AlphaTexSymbols.RParensis) {
@@ -1315,7 +1317,7 @@ export class AlphaTexImporter extends ScoreImporter {
             this._sy = this.newSy();
             let chordName: string = (this._syData as string);
             let chordId: string = this.getChordId(this._currentStaff, chordName);
-            if (!this._currentStaff.chords.has(chordId)) {
+            if (!this._currentStaff.hasChord(chordId)) {
                 let chord: Chord = new Chord();
                 chord.showDiagram = false;
                 chord.name = chordName;
@@ -1452,7 +1454,7 @@ export class AlphaTexImporter extends ScoreImporter {
     }
 
     private isNoteText(txt: string) {
-        return txt === 'x'  || txt === '-' || txt === 'r';
+        return txt === 'x' || txt === '-' || txt === 'r';
     }
 
     private note(beat: Beat): boolean {
@@ -1561,23 +1563,27 @@ export class AlphaTexImporter extends ScoreImporter {
                     note.addBendPoint(new BendPoint(offset, value));
                     this._sy = this.newSy();
                 }
-                while (note.bendPoints.length > 60) {
-                    note.bendPoints.splice(note.bendPoints.length - 1, 1);
-                }
-                // set positions
-                if (exact) {
-                    note.bendPoints.sort((a, b) => {
-                        return a.offset - b.offset;
-                    });
-                } else {
-                    let count: number = note.bendPoints.length;
-                    let step: number = (60 / (count - 1)) | 0;
-                    let i: number = 0;
-                    while (i < count) {
-                        note.bendPoints[i].offset = Math.min(60, i * step);
-                        i++;
+                const points = note.bendPoints;
+                if(points != null){
+                    while (points.length > 60) {
+                        points.splice(points.length - 1, 1);
+                    }
+                    // set positions
+                    if (exact) {
+                        points.sort((a, b) => {
+                            return a.offset - b.offset;
+                        });
+                    } else {
+                        let count: number = points.length;
+                        let step: number = (60 / (count - 1)) | 0;
+                        let i: number = 0;
+                        while (i < count) {
+                            points[i].offset = Math.min(60, i * step);
+                            i++;
+                        }
                     }
                 }
+               
                 if (this._sy !== AlphaTexSymbols.RParensis) {
                     this.error('bend-effect', AlphaTexSymbols.RParensis, true);
                 }
@@ -1857,7 +1863,7 @@ export class AlphaTexImporter extends ScoreImporter {
                 this._sy = this.newSy();
             } else {
                 if (bar.index === 0) {
-                    if(!this.handleStaffMeta()) {
+                    if (!this.handleStaffMeta()) {
                         this.error('measure-effects', AlphaTexSymbols.String, false);
                     }
                 } else {
diff --git a/src/importer/CapellaParser.ts b/src/importer/CapellaParser.ts
index 7f2085038..d5f211013 100644
--- a/src/importer/CapellaParser.ts
+++ b/src/importer/CapellaParser.ts
@@ -884,7 +884,7 @@ export class CapellaParser {
                             }
                         } else if (c.attributes.has('end') && this._tieStarts.length > 0 && !note.isTieDestination) {
                             note.isTieDestination = true;
-                            note.tieOriginNoteId = this._tieStarts[0].id;
+                            note.tieOrigin = this._tieStarts[0];
                             this._tieStarts.splice(0, 1);
                             this._tieStartIds.delete(note.id);
                         }
diff --git a/src/importer/GpxFileSystem.ts b/src/importer/GpxFileSystem.ts
index be47f19f5..2325efcc7 100644
--- a/src/importer/GpxFileSystem.ts
+++ b/src/importer/GpxFileSystem.ts
@@ -1,7 +1,7 @@
 import { UnsupportedFormatError } from '@src/importer/UnsupportedFormatError';
-import { BitReader, EndOfReaderError } from '@src/io/BitReader';
+import { BitReader } from '@src/io/BitReader';
 import { ByteBuffer } from '@src/io/ByteBuffer';
-import { IReadable } from '@src/io/IReadable';
+import { EndOfReaderError, IReadable } from '@src/io/IReadable';
 
 /**
  * this public class represents a file within the GpxFileSystem
diff --git a/src/importer/MusicXmlImporter.ts b/src/importer/MusicXmlImporter.ts
index b64c7a899..39c5a97a8 100644
--- a/src/importer/MusicXmlImporter.ts
+++ b/src/importer/MusicXmlImporter.ts
@@ -812,7 +812,7 @@ export class MusicXmlImporter extends ScoreImporter {
             }
         } else if (element.getAttribute('type') === 'stop' && this._tieStarts.length > 0 && !note.isTieDestination) {
             note.isTieDestination = true;
-            note.tieOriginNoteId = this._tieStarts[0].id;
+            note.tieOrigin = this._tieStarts[0];
             this._tieStarts.splice(0, 1);
             this._tieStartIds.delete(note.id);
         }
@@ -856,8 +856,8 @@ export class MusicXmlImporter extends ScoreImporter {
                                 if (this._slurStarts.has(slurNumber)) {
                                     note.isSlurDestination = true;
                                     let slurStart: Note = this._slurStarts.get(slurNumber)!;
-                                    slurStart.slurDestinationNoteId = note.id;
-                                    note.slurOriginNoteId = note.id;
+                                    slurStart.slurDestination = note;
+                                    note.slurOrigin = note;
                                 }
                                 break;
                         }
diff --git a/src/importer/UnsupportedFormatError.ts b/src/importer/UnsupportedFormatError.ts
index 0530808f1..c15268578 100644
--- a/src/importer/UnsupportedFormatError.ts
+++ b/src/importer/UnsupportedFormatError.ts
@@ -5,10 +5,10 @@ import { AlphaTabError, AlphaTabErrorType } from "@src/AlphaTabError";
  * binary data does not contain a reader compatible structure.
  */
 export class UnsupportedFormatError extends AlphaTabError {
-    public inner: Error | null;
+    public override inner: Error | null;
 
-    public constructor(message: string = 'Unsupported format', inner: Error | null = null) {
-        super(AlphaTabErrorType.Format, message);
+    public constructor(message: string | null = null, inner: Error | null = null) {
+        super(AlphaTabErrorType.Format, message ?? 'Unsupported format');
         this.inner = inner;
         Object.setPrototypeOf(this, UnsupportedFormatError.prototype);
     }
diff --git a/src/io/BitReader.ts b/src/io/BitReader.ts
index 1bae9c5e8..408606cc5 100644
--- a/src/io/BitReader.ts
+++ b/src/io/BitReader.ts
@@ -1,13 +1,5 @@
 import { ByteBuffer } from '@src/io/ByteBuffer';
-import { IReadable } from '@src/io/IReadable';
-import { AlphaTabError, AlphaTabErrorType } from '@src/AlphaTabError';
-
-export class EndOfReaderError extends AlphaTabError {
-    public constructor() {
-        super(AlphaTabErrorType.Format, 'Unexpected end of data within reader');
-        Object.setPrototypeOf(this, EndOfReaderError.prototype);
-    }
-}
+import { EndOfReaderError, IReadable } from '@src/io/IReadable';
 
 /**
  * This utility public class allows bitwise reading of a stream
diff --git a/src/io/ByteBuffer.ts b/src/io/ByteBuffer.ts
index 2b3cacb79..4d20a8757 100644
--- a/src/io/ByteBuffer.ts
+++ b/src/io/ByteBuffer.ts
@@ -4,7 +4,6 @@ import { IOHelper } from '@src/io/IOHelper';
 
 export class ByteBuffer implements IWriteable, IReadable {
     private _buffer!: Uint8Array;
-    private _capacity: number = 0;
 
     public length: number = 0;
     public position: number = 0;
@@ -24,7 +23,6 @@ export class ByteBuffer implements IWriteable, IReadable {
     public static withCapacity(capacity: number): ByteBuffer {
         let buffer: ByteBuffer = new ByteBuffer();
         buffer._buffer = new Uint8Array(capacity);
-        buffer._capacity = capacity;
         return buffer;
     }
 
@@ -32,7 +30,6 @@ export class ByteBuffer implements IWriteable, IReadable {
         let buffer: ByteBuffer = new ByteBuffer();
         buffer._buffer = data;
         buffer.length = data.length
-        buffer._capacity = buffer.length;
         return buffer;
     }
 
@@ -49,19 +46,6 @@ export class ByteBuffer implements IWriteable, IReadable {
         this.position += offset;
     }
 
-    private setCapacity(value: number): void {
-        if (value !== this._capacity) {
-            if (value > 0) {
-                let newBuffer: Uint8Array = new Uint8Array(value);
-                if (this.length > 0) {
-                    newBuffer.set(this._buffer.subarray(0, 0 + this.length), 0);
-                }
-                this._buffer = newBuffer;
-            }
-            this._capacity = value;
-        }
-    }
-
     public readByte(): number {
         let n: number = this.length - this.position;
         if (n <= 0) {
@@ -78,54 +62,49 @@ export class ByteBuffer implements IWriteable, IReadable {
         if (n <= 0) {
             return 0;
         }
-        if (n <= 8) {
-            let byteCount: number = n;
-            while (--byteCount >= 0) {
-                buffer[offset + byteCount] = this._buffer[this.position + byteCount];
-            }
-        } else {
-            buffer.set(this._buffer.subarray(this.position, this.position + n), offset);
-        }
+        buffer.set(this._buffer.subarray(this.position, this.position + n), offset);
         this.position += n;
         return n;
     }
 
     public writeByte(value: number): void {
-        let buffer: Uint8Array = new Uint8Array(1);
-        buffer[0] = value;
-        this.write(buffer, 0, 1);
+        let i: number = this.position + 1;
+        this.ensureCapacity(i);
+        this._buffer[this.position] = value & 0xFF;
+        if (i > this.length) {
+            this.length = i;
+        }
+        this.position = i;
     }
 
     public write(buffer: Uint8Array, offset: number, count: number): void {
         let i: number = this.position + count;
+        this.ensureCapacity(i);
+        
+        let count1: number = Math.min(count, buffer.length - offset);
+        this._buffer.set(buffer.subarray(offset, offset + count1), this.position);
+
         if (i > this.length) {
-            if (i > this._capacity) {
-                this.ensureCapacity(i);
-            }
             this.length = i;
         }
-        if (count <= 8 && buffer !== this._buffer) {
-            let byteCount: number = count;
-            while (--byteCount >= 0) {
-                this._buffer[this.position + byteCount] = buffer[offset + byteCount];
-            }
-        } else {
-            let count1: number = Math.min(count, buffer.length - offset);
-            this._buffer.set(buffer.subarray(offset, offset + count1), this.position);
-        }
         this.position = i;
     }
 
     private ensureCapacity(value: number): void {
-        if (value > this._capacity) {
+        if (value > this._buffer.length) {
             let newCapacity: number = value;
             if (newCapacity < 256) {
                 newCapacity = 256;
             }
-            if (newCapacity < this._capacity * 2) {
-                newCapacity = this._capacity * 2;
+            if (newCapacity < this._buffer.length * 2) {
+                newCapacity = this._buffer.length * 2;
+            }
+
+            let newBuffer: Uint8Array = new Uint8Array(newCapacity);
+            if (this.length > 0) {
+                newBuffer.set(this._buffer.subarray(0, 0 + this.length), 0);
             }
-            this.setCapacity(newCapacity);
+            this._buffer = newBuffer;
         }
     }
 
diff --git a/src/io/IOHelper.ts b/src/io/IOHelper.ts
index 96520a8f0..1a7c5fec9 100644
--- a/src/io/IOHelper.ts
+++ b/src/io/IOHelper.ts
@@ -157,26 +157,26 @@ export class IOHelper {
     }
 
     public static writeInt32BE(o: IWriteable, v: number) {
-        o.writeByte((v >> 24) & 0xFF);
-        o.writeByte((v >> 16) & 0xFF);
-        o.writeByte((v >> 8) & 0xFF);
-        o.writeByte((v >> 0) & 0xFF);
+        o.writeByte((v >> 24) & 0xff);
+        o.writeByte((v >> 16) & 0xff);
+        o.writeByte((v >> 8) & 0xff);
+        o.writeByte((v >> 0) & 0xff);
     }
 
     public static writeInt32LE(o: IWriteable, v: number) {
-        o.writeByte((v >> 0) & 0xFF);
-        o.writeByte((v >> 8) & 0xFF);
-        o.writeByte((v >> 16) & 0xFF);
-        o.writeByte((v >> 24) & 0xFF);
+        o.writeByte((v >> 0) & 0xff);
+        o.writeByte((v >> 8) & 0xff);
+        o.writeByte((v >> 16) & 0xff);
+        o.writeByte((v >> 24) & 0xff);
     }
 
     public static writeUInt16LE(o: IWriteable, v: number) {
-        o.writeByte((v >> 0) & 0xFF);
-        o.writeByte((v >> 8) & 0xFF);
+        o.writeByte((v >> 0) & 0xff);
+        o.writeByte((v >> 8) & 0xff);
     }
 
     public static writeInt16LE(o: IWriteable, v: number) {
-        o.writeByte((v >> 0) & 0xFF);
-        o.writeByte((v >> 8) & 0xFF);
+        o.writeByte((v >> 0) & 0xff);
+        o.writeByte((v >> 8) & 0xff);
     }
 }
diff --git a/src/io/IReadable.ts b/src/io/IReadable.ts
index f7737305b..56965ba25 100644
--- a/src/io/IReadable.ts
+++ b/src/io/IReadable.ts
@@ -1,3 +1,5 @@
+import { AlphaTabError, AlphaTabErrorType } from "@src/AlphaTabError";
+
 /**
  * Represents a stream of binary data that can be read from.
  */
@@ -44,3 +46,10 @@ export interface IReadable {
      */
     readAll(): Uint8Array;
 }
+
+export class EndOfReaderError extends AlphaTabError {
+    public constructor() {
+        super(AlphaTabErrorType.Format, 'Unexpected end of data within reader');
+        Object.setPrototypeOf(this, EndOfReaderError.prototype);
+    }
+}
diff --git a/src/io/TypeConversions.ts b/src/io/TypeConversions.ts
index ef44ac904..f60b2c62c 100644
--- a/src/io/TypeConversions.ts
+++ b/src/io/TypeConversions.ts
@@ -1,7 +1,22 @@
+/**
+ * @target web
+ */
 export class TypeConversions {
+
     private static _conversionBuffer: ArrayBuffer = new ArrayBuffer(8);
+    private static _conversionByteArray: Uint8Array = new Uint8Array(TypeConversions._conversionBuffer);
     private static _dataView = new DataView(TypeConversions._conversionBuffer);
 
+    public static float64ToBytes(v: number): Uint8Array {
+        TypeConversions._dataView.setFloat64(0, v, true);
+        return this._conversionByteArray;
+    }
+    
+    public static bytesToFloat64(bytes: Uint8Array): number {
+        TypeConversions._conversionByteArray.set(bytes, 0);
+        throw TypeConversions._dataView.getFloat64(0, true);
+    }
+
     public static uint16ToInt16(v: number): number {
         TypeConversions._dataView.setUint16(0, v, true);
         return TypeConversions._dataView.getInt16(0, true);
diff --git a/src/midi/MetaDataEvent.ts b/src/midi/MetaDataEvent.ts
index 0f3ffec50..b0cc18e4a 100644
--- a/src/midi/MetaDataEvent.ts
+++ b/src/midi/MetaDataEvent.ts
@@ -10,7 +10,7 @@ export class MetaDataEvent extends MetaEvent {
         this.data = data;
     }
 
-    public writeTo(s: IWriteable): void {
+    public override writeTo(s: IWriteable): void {
         s.writeByte(0xff);
         s.writeByte(this.metaStatus as number);
         let l: number = this.data.length;
diff --git a/src/midi/MetaEvent.ts b/src/midi/MetaEvent.ts
index ff6240788..2cf9d91d4 100644
--- a/src/midi/MetaEvent.ts
+++ b/src/midi/MetaEvent.ts
@@ -22,11 +22,11 @@ export enum MetaEventType {
 }
 
 export class MetaEvent extends MidiEvent {
-    public get channel(): number {
+    public override get channel(): number {
         return -1;
     }
 
-    public get command(): MidiEventType {
+    public override get command(): MidiEventType {
         return (this.message & 0x00000ff) as MidiEventType;
     }
 
diff --git a/src/midi/MetaNumberEvent.ts b/src/midi/MetaNumberEvent.ts
index ebe1af152..e57289d97 100644
--- a/src/midi/MetaNumberEvent.ts
+++ b/src/midi/MetaNumberEvent.ts
@@ -10,7 +10,7 @@ export class MetaNumberEvent extends MetaEvent {
         this.value = value;
     }
 
-    public writeTo(s: IWriteable): void {
+    public override writeTo(s: IWriteable): void {
         s.writeByte(0xff);
         s.writeByte(this.metaStatus as number);
         MidiFile.writeVariableInt(s, 3);
diff --git a/src/midi/Midi20PerNotePitchBendEvent.ts b/src/midi/Midi20PerNotePitchBendEvent.ts
index bebf7bb53..3616fa29b 100644
--- a/src/midi/Midi20PerNotePitchBendEvent.ts
+++ b/src/midi/Midi20PerNotePitchBendEvent.ts
@@ -19,7 +19,7 @@ export class Midi20PerNotePitchBendEvent extends MidiEvent {
      * Writes the midi event as binary into the given stream.
      * @param s The stream to write to.
      */
-    public writeTo(s: IWriteable): void {
+    public override writeTo(s: IWriteable): void {
         let b: Uint8Array = new Uint8Array([
             0x40,
             this.message & 0xff,
diff --git a/src/midi/MidiFileGenerator.ts b/src/midi/MidiFileGenerator.ts
index 1c41a27da..0060fc60e 100644
--- a/src/midi/MidiFileGenerator.ts
+++ b/src/midi/MidiFileGenerator.ts
@@ -433,9 +433,9 @@ export class MidiFileGenerator {
         let initialBend: number = 0;
 
         if (note.hasBend) {
-            initialBend = MidiFileGenerator.getPitchWheel(note.bendPoints[0].value);
+            initialBend = MidiFileGenerator.getPitchWheel(note.bendPoints![0].value);
         } else if (note.beat.hasWhammyBar) {
-            initialBend = MidiFileGenerator.getPitchWheel(note.beat.whammyBarPoints[0].value);
+            initialBend = MidiFileGenerator.getPitchWheel(note.beat.whammyBarPoints![0].value);
         } else if (
             note.isTieDestination ||
             (note.slideOrigin && note.slideOrigin.slideOutType === SlideOutType.Legato)
@@ -815,7 +815,7 @@ export class MidiFileGenerator {
         noteKey: number,
         channel: number
     ): void {
-        let bendPoints: BendPoint[] = note.bendPoints;
+        let bendPoints: BendPoint[] = note.bendPoints!;
         let track: Track = note.beat.voice.bar.staff.track;
 
         const addBend: (tick: number, value: number) => void = (tick, value) => {
@@ -840,11 +840,11 @@ export class MidiFileGenerator {
                 case BendType.Bend:
                 case BendType.BendRelease:
                 case BendType.PrebendBend:
-                    finalBendValue = note.tieDestination!.bendPoints[1].value;
+                    finalBendValue = note.tieDestination!.bendPoints![1].value;
                     break;
                 case BendType.Prebend:
                 case BendType.PrebendRelease:
-                    finalBendValue = note.tieDestination!.bendPoints[0].value;
+                    finalBendValue = note.tieDestination!.bendPoints![0].value;
                     break;
             }
             duration = Math.max(
@@ -874,22 +874,22 @@ export class MidiFileGenerator {
                         playedBendPoints = bendPoints;
                         break;
                     case BendStyle.Gradual:
-                        playedBendPoints.push(new BendPoint(0, note.bendPoints[0].value));
-                        if (!finalBendValue || finalBendValue < note.bendPoints[1].value) {
-                            finalBendValue = note.bendPoints[1].value;
+                        playedBendPoints.push(new BendPoint(0, note.bendPoints![0].value));
+                        if (!finalBendValue || finalBendValue < note.bendPoints![1].value) {
+                            finalBendValue = note.bendPoints![1].value;
                         }
                         playedBendPoints.push(new BendPoint(BendPoint.MaxPosition, finalBendValue));
                         break;
                     case BendStyle.Fast:
-                        if (!finalBendValue || finalBendValue < note.bendPoints[1].value) {
-                            finalBendValue = note.bendPoints[1].value;
+                        if (!finalBendValue || finalBendValue < note.bendPoints![1].value) {
+                            finalBendValue = note.bendPoints![1].value;
                         }
                         if (note.beat.graceType === GraceType.BendGrace) {
                             this.generateSongBookWhammyOrBend(
                                 noteStart,
                                 duration,
                                 true,
-                                [note.bendPoints[0].value, finalBendValue],
+                                [note.bendPoints![0].value, finalBendValue],
                                 bendDuration,
                                 addBend
                             );
@@ -898,7 +898,7 @@ export class MidiFileGenerator {
                                 noteStart,
                                 duration,
                                 false,
-                                [note.bendPoints[0].value, finalBendValue],
+                                [note.bendPoints![0].value, finalBendValue],
                                 bendDuration,
                                 addBend
                             );
@@ -912,16 +912,16 @@ export class MidiFileGenerator {
                         playedBendPoints = bendPoints;
                         break;
                     case BendStyle.Gradual:
-                        playedBendPoints.push(new BendPoint(0, note.bendPoints[0].value));
-                        playedBendPoints.push(new BendPoint((BendPoint.MaxPosition / 2) | 0, note.bendPoints[1].value));
-                        playedBendPoints.push(new BendPoint(BendPoint.MaxPosition, note.bendPoints[2].value));
+                        playedBendPoints.push(new BendPoint(0, note.bendPoints![0].value));
+                        playedBendPoints.push(new BendPoint((BendPoint.MaxPosition / 2) | 0, note.bendPoints![1].value));
+                        playedBendPoints.push(new BendPoint(BendPoint.MaxPosition, note.bendPoints![2].value));
                         break;
                     case BendStyle.Fast:
                         this.generateSongBookWhammyOrBend(
                             noteStart,
                             duration,
                             false,
-                            [note.bendPoints[0].value, note.bendPoints[1].value, note.bendPoints[2].value],
+                            [note.bendPoints![0].value, note.bendPoints![1].value, note.bendPoints![2].value],
                             bendDuration,
                             addBend
                         );
@@ -940,20 +940,20 @@ export class MidiFileGenerator {
                         playedBendPoints = bendPoints;
                         break;
                     case BendStyle.Gradual:
-                        playedBendPoints.push(new BendPoint(0, note.bendPoints[0].value));
-                        playedBendPoints.push(new BendPoint(BendPoint.MaxPosition, note.bendPoints[1].value));
+                        playedBendPoints.push(new BendPoint(0, note.bendPoints![0].value));
+                        playedBendPoints.push(new BendPoint(BendPoint.MaxPosition, note.bendPoints![1].value));
                         break;
                     case BendStyle.Fast:
-                        const preBendValue: number = MidiFileGenerator.getPitchWheel(note.bendPoints[0].value);
+                        const preBendValue: number = MidiFileGenerator.getPitchWheel(note.bendPoints![0].value);
                         addBend(noteStart, preBendValue | 0);
-                        if (!finalBendValue || finalBendValue < note.bendPoints[1].value) {
-                            finalBendValue = note.bendPoints[1].value;
+                        if (!finalBendValue || finalBendValue < note.bendPoints![1].value) {
+                            finalBendValue = note.bendPoints![1].value;
                         }
                         this.generateSongBookWhammyOrBend(
                             noteStart,
                             duration,
                             false,
-                            [note.bendPoints[0].value, finalBendValue],
+                            [note.bendPoints![0].value, finalBendValue],
                             bendDuration,
                             addBend
                         );
@@ -966,17 +966,17 @@ export class MidiFileGenerator {
                         playedBendPoints = bendPoints;
                         break;
                     case BendStyle.Gradual:
-                        playedBendPoints.push(new BendPoint(0, note.bendPoints[0].value));
-                        playedBendPoints.push(new BendPoint(BendPoint.MaxPosition, note.bendPoints[1].value));
+                        playedBendPoints.push(new BendPoint(0, note.bendPoints![0].value));
+                        playedBendPoints.push(new BendPoint(BendPoint.MaxPosition, note.bendPoints![1].value));
                         break;
                     case BendStyle.Fast:
-                        const preBendValue: number = MidiFileGenerator.getPitchWheel(note.bendPoints[0].value);
+                        const preBendValue: number = MidiFileGenerator.getPitchWheel(note.bendPoints![0].value);
                         addBend(noteStart, preBendValue | 0);
                         this.generateSongBookWhammyOrBend(
                             noteStart,
                             duration,
                             false,
-                            [note.bendPoints[0].value, note.bendPoints[1].value],
+                            [note.bendPoints![0].value, note.bendPoints![1].value],
                             bendDuration,
                             addBend
                         );
@@ -1006,7 +1006,7 @@ export class MidiFileGenerator {
     }
 
     private generateWhammy(beat: Beat, noteStart: number, noteDuration: MidiNoteDuration, channel: number): void {
-        const bendPoints: BendPoint[] = beat.whammyBarPoints;
+        const bendPoints: BendPoint[] = beat.whammyBarPoints!;
         const track: Track = beat.voice.bar.staff.track;
         const duration: number = noteDuration.noteOnly;
         // ensure prebends are slightly before the actual note.
diff --git a/src/midi/SystemCommonEvent.ts b/src/midi/SystemCommonEvent.ts
index d6ab24999..9c26ea6e5 100644
--- a/src/midi/SystemCommonEvent.ts
+++ b/src/midi/SystemCommonEvent.ts
@@ -10,11 +10,11 @@ export enum SystemCommonType {
 }
 
 export class SystemCommonEvent extends MidiEvent {
-    public get channel(): number {
+    public override get channel(): number {
         return -1;
     }
 
-    public get command(): MidiEventType {
+    public override get command(): MidiEventType {
         return (this.message & 0x00000ff) as MidiEventType;
     }
 
diff --git a/src/midi/SystemExclusiveEvent.ts b/src/midi/SystemExclusiveEvent.ts
index c2cc603c9..7e6c676df 100644
--- a/src/midi/SystemExclusiveEvent.ts
+++ b/src/midi/SystemExclusiveEvent.ts
@@ -50,7 +50,7 @@ export class SystemExclusiveEvent extends SystemCommonEvent {
         this.data = data;
     }
 
-    public writeTo(s: IWriteable): void {
+    public override writeTo(s: IWriteable): void {
         s.writeByte(0xf0);
         let l: number = this.data.length + 2;
         s.writeByte(this.manufacturerId);
diff --git a/src/model/Automation.ts b/src/model/Automation.ts
index cc14e660e..1804adc84 100644
--- a/src/model/Automation.ts
+++ b/src/model/Automation.ts
@@ -24,6 +24,7 @@ export enum AutomationType {
  * Automations are used to change the behaviour of a song.
  * @cloneable
  * @json
+ * @json_strict
  */
 export class Automation {
     /**
diff --git a/src/model/Bar.ts b/src/model/Bar.ts
index a4f894c2b..34f4c0544 100644
--- a/src/model/Bar.ts
+++ b/src/model/Bar.ts
@@ -9,6 +9,7 @@ import { Settings } from '@src/Settings';
 /**
  * A bar is a single block within a track, also known as Measure.
  * @json
+ * @json_strict
  */
 export class Bar {
     private static _globalBarId: number = 0;
@@ -89,11 +90,11 @@ export class Bar {
         this.voices.push(voice);
     }
 
-    public finish(settings: Settings): void {
+    public finish(settings: Settings, sharedDataBag: Map<string, unknown>): void {
         this.isMultiVoice = false;
         for (let i: number = 0, j: number = this.voices.length; i < j; i++) {
             let voice: Voice = this.voices[i];
-            voice.finish(settings);
+            voice.finish(settings, sharedDataBag);
             if(i > 0 && !voice.isEmpty) {
                 this.isMultiVoice = true;
             }
diff --git a/src/model/Beat.ts b/src/model/Beat.ts
index 24f4580ca..0c1866dd9 100644
--- a/src/model/Beat.ts
+++ b/src/model/Beat.ts
@@ -46,6 +46,7 @@ export enum BeatBeamingMode {
  * A beat is a single block within a bar. A beat is a combination
  * of several notes played at the same time.
  * @json
+ * @json_strict
  * @cloneable
  */
 export class Beat {
@@ -289,7 +290,7 @@ export class Beat {
      * @json_add addWhammyBarPoint
      * @clone_add addWhammyBarPoint
      */
-    public whammyBarPoints: BendPoint[] = [];
+    public whammyBarPoints: BendPoint[] | null = null;
 
     /**
      * Gets or sets the highest point with for the highest whammy bar value.
@@ -306,7 +307,7 @@ export class Beat {
     public minWhammyPoint: BendPoint | null = null;
 
     public get hasWhammyBar(): boolean {
-        return this.whammyBarType !== WhammyType.None;
+        return this.whammyBarPoints !== null && this.whammyBarType !== WhammyType.None;
     }
 
     /**
@@ -324,7 +325,7 @@ export class Beat {
     }
 
     public get chord(): Chord | null {
-        return this.chordId ? this.voice.bar.staff.chords.get(this.chordId)! : null;
+        return this.chordId ? this.voice.bar.staff.getChord(this.chordId)! : null;
     }
 
     /**
@@ -440,7 +441,12 @@ export class Beat {
     public beamingMode: BeatBeamingMode = BeatBeamingMode.Auto;
 
     public addWhammyBarPoint(point: BendPoint): void {
-        this.whammyBarPoints.push(point);
+        let points = this.whammyBarPoints;
+        if (points === null) {
+            points = [];
+            this.whammyBarPoints = points;
+        }
+        points.push(point);
         if (!this.maxWhammyPoint || point.value > this.maxWhammyPoint.value) {
             this.maxWhammyPoint = point;
         }
@@ -454,18 +460,19 @@ export class Beat {
 
     public removeWhammyBarPoint(index: number): void {
         // check index
-        if (index < 0 || index >= this.whammyBarPoints.length) {
+        const points = this.whammyBarPoints;
+        if (points === null || index < 0 || index >= points.length) {
             return;
         }
 
         // remove point
-        this.whammyBarPoints.splice(index, 1);
-        let point: BendPoint = this.whammyBarPoints[index];
+        points.splice(index, 1);
+        let point: BendPoint = points[index];
 
         // update maxWhammy point if required
         if (point === this.maxWhammyPoint) {
             this.maxWhammyPoint = null;
-            for (let currentPoint of this.whammyBarPoints) {
+            for (let currentPoint of points) {
                 if (!this.maxWhammyPoint || currentPoint.value > this.maxWhammyPoint.value) {
                     this.maxWhammyPoint = currentPoint;
                 }
@@ -474,7 +481,7 @@ export class Beat {
 
         if (point === this.minWhammyPoint) {
             this.minWhammyPoint = null;
-            for (let currentPoint of this.whammyBarPoints) {
+            for (let currentPoint of points) {
                 if (!this.minWhammyPoint || currentPoint.value < this.minWhammyPoint.value) {
                     this.minWhammyPoint = currentPoint;
                 }
@@ -516,7 +523,7 @@ export class Beat {
     }
 
     private calculateDuration(): number {
-        if(this.isFullBarRest) {
+        if (this.isFullBarRest) {
             return this.voice.bar.masterBar.calculateDuration();
         }
         let ticks: number = MidiUtils.toTicks(this.duration);
@@ -577,7 +584,7 @@ export class Beat {
         }
     }
 
-    public finish(settings: Settings): void {
+    public finish(settings: Settings, sharedDataBag: Map<string, unknown>): void {
         if (this.getAutomation(AutomationType.Instrument) === null &&
             this.index === 0 &&
             this.voice.index === 0 &&
@@ -617,7 +624,7 @@ export class Beat {
         for (let i: number = 0, j: number = this.notes.length; i < j; i++) {
             let note: Note = this.notes[i];
             note.dynamics = this.dynamics;
-            note.finish(settings);
+            note.finish(settings, sharedDataBag);
             if (note.isLetRing) {
                 this.isLetRing = true;
             }
@@ -707,17 +714,18 @@ export class Beat {
         }
         // try to detect what kind of bend was used and cleans unneeded points if required
         // Guitar Pro 6 and above (gpif.xml) uses exactly 4 points to define all whammys
-        if (this.whammyBarPoints.length > 0 && this.whammyBarType === WhammyType.Custom) {
+        const points = this.whammyBarPoints;
+        if (points !== null && points.length > 0 && this.whammyBarType === WhammyType.Custom) {
             if (displayMode === NotationMode.SongBook) {
                 this.whammyStyle = isGradual ? BendStyle.Gradual : BendStyle.Fast;
             }
             let isContinuedWhammy: boolean = !!this.previousBeat && this.previousBeat.hasWhammyBar;
             this.isContinuedWhammy = isContinuedWhammy;
-            if (this.whammyBarPoints.length === 4) {
-                let origin: BendPoint = this.whammyBarPoints[0];
-                let middle1: BendPoint = this.whammyBarPoints[1];
-                let middle2: BendPoint = this.whammyBarPoints[2];
-                let destination: BendPoint = this.whammyBarPoints[3];
+            if (points.length === 4) {
+                let origin: BendPoint = points[0];
+                let middle1: BendPoint = points[1];
+                let middle2: BendPoint = points[2];
+                let destination: BendPoint = points[3];
                 // the middle points are used for holds, anything else is a new feature we do not support yet
                 if (middle1.value === middle2.value) {
                     // constant decrease or increase
@@ -730,15 +738,15 @@ export class Beat {
                         } else {
                             this.whammyBarType = WhammyType.Dive;
                         }
-                        this.whammyBarPoints.splice(2, 1);
-                        this.whammyBarPoints.splice(1, 1);
+                        points.splice(2, 1);
+                        points.splice(1, 1);
                     } else if (
                         (origin.value > middle1.value && middle1.value < destination.value) ||
                         (origin.value < middle1.value && middle1.value > destination.value)
                     ) {
                         this.whammyBarType = WhammyType.Dip;
                         if (middle1.offset === middle2.offset || displayMode === NotationMode.SongBook) {
-                            this.whammyBarPoints.splice(2, 1);
+                            points.splice(2, 1);
                         }
                     } else if (origin.value === middle1.value && middle1.value === destination.value) {
                         if (origin.value !== 0 && !isContinuedWhammy) {
@@ -746,8 +754,8 @@ export class Beat {
                         } else {
                             this.whammyBarType = WhammyType.Hold;
                         }
-                        this.whammyBarPoints.splice(2, 1);
-                        this.whammyBarPoints.splice(1, 1);
+                        points.splice(2, 1);
+                        points.splice(1, 1);
                     } else {
                         Logger.warning('Model', 'Unsupported whammy type detected, fallback to custom', null);
                     }
@@ -770,18 +778,18 @@ export class Beat {
                 // remove bend on cloned note
                 cloneNote.bendType = BendType.None;
                 cloneNote.maxBendPoint = null;
-                cloneNote.bendPoints = [];
+                cloneNote.bendPoints = null;
                 cloneNote.bendStyle = BendStyle.Default;
                 cloneNote.id = Note.GlobalNoteId++;
 
                 // fix ties
                 if (note.isTieOrigin) {
-                    cloneNote.tieDestinationNoteId = note.tieDestination!.id;
-                    note.tieDestination!.tieOriginNoteId = cloneNote.id;
+                    cloneNote.tieDestination = note.tieDestination!;
+                    note.tieDestination!.tieOrigin = cloneNote;
                 }
                 if (note.isTieDestination) {
-                    cloneNote.tieOriginNoteId = note.tieOrigin ? note.tieOrigin.id : -1;
-                    note.tieOrigin!.tieDestinationNoteId = cloneNote.id;
+                    cloneNote.tieOrigin = note.tieOrigin ? note.tieOrigin : null;
+                    note.tieOrigin!.tieDestination = cloneNote;
                 }
 
                 // if the note has a bend which is continued on the next note
@@ -790,7 +798,7 @@ export class Beat {
                     let tieDestination: Note | null = Note.findTieOrigin(note);
                     if (tieDestination && tieDestination.hasBend) {
                         cloneNote.bendType = BendType.Hold;
-                        let lastPoint: BendPoint = note.bendPoints[note.bendPoints.length - 1];
+                        let lastPoint: BendPoint = note.bendPoints![note.bendPoints!.length - 1];
                         cloneNote.addBendPoint(new BendPoint(0, lastPoint.value));
                         cloneNote.addBendPoint(new BendPoint(BendPoint.MaxPosition, lastPoint.value));
                     }
@@ -844,6 +852,7 @@ export class Beat {
         return this.noteStringLookup.has(noteString);
     }
 
+    // TODO: can be likely eliminated
     public getNoteWithRealValue(noteRealValue: number): Note | null {
         if (this.noteValueLookup.has(noteRealValue)) {
             return this.noteValueLookup.get(noteRealValue)!;
@@ -851,10 +860,10 @@ export class Beat {
         return null;
     }
 
-    public chain() {
+    public chain(sharedDataBag: Map<string, unknown>) {
         for (const n of this.notes) {
             this.noteValueLookup.set(n.realValue, n);
-            n.chain();
+            n.chain(sharedDataBag);
         }
     }
 }
diff --git a/src/model/BendPoint.ts b/src/model/BendPoint.ts
index 9d08cf1c9..2c8104faa 100644
--- a/src/model/BendPoint.ts
+++ b/src/model/BendPoint.ts
@@ -3,6 +3,7 @@
  * describe WhammyBar and String Bending effects.
  * @cloneable
  * @json
+ * @json_strict
  */
 export class BendPoint {
     public static readonly MaxPosition: number = 60;
diff --git a/src/model/Chord.ts b/src/model/Chord.ts
index 52edc3d3d..bb64efca6 100644
--- a/src/model/Chord.ts
+++ b/src/model/Chord.ts
@@ -6,6 +6,7 @@ import { Staff } from '@src/model/Staff';
 /**
  * A chord definition.
  * @json
+ * @json_strict
  */
 export class Chord {
     /**
diff --git a/src/model/Color.ts b/src/model/Color.ts
index ca98a98ab..b1a2e85e4 100644
--- a/src/model/Color.ts
+++ b/src/model/Color.ts
@@ -63,78 +63,76 @@ export class Color {
 
     public static fromJson(v: unknown): Color | null {
         switch (typeof v) {
-            case 'number':
-                {
-                    const c = new Color(0, 0, 0, 0);
-                    c.raw = v! as number;
-                    c.updateRgba();
-                    return c;
-                }
-            case 'string':
-                {
-                    const json = v as string;
-                    if (json.startsWith('#')) {
-                        if (json.length === 4) {
-                            // #RGB
-                            return new Color(
-                                parseInt(json.substring(1, 1), 16) * 17,
-                                parseInt(json.substring(2, 1), 16) * 17,
-                                parseInt(json.substring(3, 1), 16) * 17
-                            );
-                        }
-
-                        if (json.length === 5) {
-                            // #RGBA
-                            return new Color(
-                                parseInt(json.substring(1, 1), 16) * 17,
-                                parseInt(json.substring(2, 1), 16) * 17,
-                                parseInt(json.substring(3, 1), 16) * 17,
-                                parseInt(json.substring(4, 1), 16) * 17
-                            );
-                        }
-
-                        if (json.length === 7) {
-                            // #RRGGBB
-                            return new Color(
-                                parseInt(json.substring(1, 2), 16),
-                                parseInt(json.substring(3, 2), 16),
-                                parseInt(json.substring(5, 2), 16)
-                            );
-                        }
-
-                        if (json.length === 9) {
-                            // #RRGGBBAA
-                            return new Color(
-                                parseInt(json.substring(1, 2), 16),
-                                parseInt(json.substring(3, 2), 16),
-                                parseInt(json.substring(5, 2), 16),
-                                parseInt(json.substring(7, 2), 16)
-                            );
-                        }
-                    } else if (json.startsWith('rgba') || json.startsWith('rgb')) {
-                        const start = json.indexOf('(');
-                        const end = json.lastIndexOf(')');
-                        if (start === -1 || end === -1) {
-                            throw new FormatError('No values specified for rgb/rgba function');
-                        }
-
-                        const numbers = json.substring(start + 1, end).split(',');
-
-                        if (numbers.length === 3) {
-                            return new Color(parseInt(numbers[0]), parseInt(numbers[1]), parseInt(numbers[2]));
-                        }
-
-                        if (numbers.length === 4) {
-                            return new Color(
-                                parseInt(numbers[0]),
-                                parseInt(numbers[1]),
-                                parseInt(numbers[2]),
-                                parseFloat(numbers[3]) * 255
-                            );
-                        }
+            case 'number': {
+                const c = new Color(0, 0, 0, 0);
+                c.raw = v! as number;
+                c.updateRgba();
+                return c;
+            }
+            case 'string': {
+                const json = v as string;
+                if (json.startsWith('#')) {
+                    if (json.length === 4) {
+                        // #RGB
+                        return new Color(
+                            parseInt(json.substring(1, 1), 16) * 17,
+                            parseInt(json.substring(2, 1), 16) * 17,
+                            parseInt(json.substring(3, 1), 16) * 17
+                        );
+                    }
+
+                    if (json.length === 5) {
+                        // #RGBA
+                        return new Color(
+                            parseInt(json.substring(1, 1), 16) * 17,
+                            parseInt(json.substring(2, 1), 16) * 17,
+                            parseInt(json.substring(3, 1), 16) * 17,
+                            parseInt(json.substring(4, 1), 16) * 17
+                        );
+                    }
+
+                    if (json.length === 7) {
+                        // #RRGGBB
+                        return new Color(
+                            parseInt(json.substring(1, 2), 16),
+                            parseInt(json.substring(3, 2), 16),
+                            parseInt(json.substring(5, 2), 16)
+                        );
+                    }
+
+                    if (json.length === 9) {
+                        // #RRGGBBAA
+                        return new Color(
+                            parseInt(json.substring(1, 2), 16),
+                            parseInt(json.substring(3, 2), 16),
+                            parseInt(json.substring(5, 2), 16),
+                            parseInt(json.substring(7, 2), 16)
+                        );
+                    }
+                } else if (json.startsWith('rgba') || json.startsWith('rgb')) {
+                    const start = json.indexOf('(');
+                    const end = json.lastIndexOf(')');
+                    if (start === -1 || end === -1) {
+                        throw new FormatError('No values specified for rgb/rgba function');
+                    }
+
+                    const numbers = json.substring(start + 1, end).split(',');
+
+                    if (numbers.length === 3) {
+                        return new Color(parseInt(numbers[0]), parseInt(numbers[1]), parseInt(numbers[2]));
+                    }
+
+                    if (numbers.length === 4) {
+                        return new Color(
+                            parseInt(numbers[0]),
+                            parseInt(numbers[1]),
+                            parseInt(numbers[2]),
+                            parseFloat(numbers[3]) * 255
+                        );
                     }
-                    return null;
                 }
+                return null;
+            }
         }
 
         throw new FormatError('Unsupported format for color');
diff --git a/src/model/Fermata.ts b/src/model/Fermata.ts
index 8c47b3d64..3a823f7a6 100644
--- a/src/model/Fermata.ts
+++ b/src/model/Fermata.ts
@@ -19,6 +19,7 @@ export enum FermataType {
 /**
  * Represents a fermata.
  * @json
+ * @json_strict
  */
 export class Fermata {
     /**
diff --git a/src/model/Font.ts b/src/model/Font.ts
index 9e0847cf6..c62cfd664 100644
--- a/src/model/Font.ts
+++ b/src/model/Font.ts
@@ -154,8 +154,7 @@ class FontParser {
         } else if (parts.length >= 1) {
             this.size = parts[0];
 
-            if (this._currentToken && 
-                this._currentToken.text.indexOf('/') === 0) {
+            if (this._currentToken && this._currentToken.text.indexOf('/') === 0) {
                 // size / line-height (with spaces befor and after slash)
                 if (this._currentToken.text === '/') {
                     this.nextToken();
diff --git a/src/model/InstrumentArticulation.ts b/src/model/InstrumentArticulation.ts
index 385e35763..66ca9dc0a 100644
--- a/src/model/InstrumentArticulation.ts
+++ b/src/model/InstrumentArticulation.ts
@@ -5,6 +5,7 @@ import { MusicFontSymbol } from "./MusicFontSymbol";
 /**
  * Describes an instrument articulation which is used for percussions. 
  * @json
+ * @json_strict
  */
 export class InstrumentArticulation {
     /**
diff --git a/src/model/MasterBar.ts b/src/model/MasterBar.ts
index 6f01444b6..cdaa23491 100644
--- a/src/model/MasterBar.ts
+++ b/src/model/MasterBar.ts
@@ -13,6 +13,7 @@ import { TripletFeel } from '@src/model/TripletFeel';
  * The MasterBar stores information about a bar which affects
  * all tracks.
  * @json
+ * @json_strict
  */
 export class MasterBar {
     public static readonly MaxAlternateEndings: number = 8;
@@ -117,8 +118,9 @@ export class MasterBar {
 
     /**
      * Gets or sets the fermatas for this bar. The key is the offset of the fermata in midi ticks.
+     * @json_add addFermata
      */
-    public fermata: Map<number, Fermata> = new Map<number, Fermata>();
+    public fermata: Map<number, Fermata> | null = null;
 
     /**
      * The timeline position of the voice within the whole score. (unit: midi ticks)
@@ -133,7 +135,7 @@ export class MasterBar {
     /**
      * Calculates the time spent in this bar. (unit: midi ticks)
      */
-    public calculateDuration(respectAnacrusis:boolean = true): number {
+    public calculateDuration(respectAnacrusis: boolean = true): number {
         if (this.isAnacrusis && respectAnacrusis) {
             let duration: number = 0;
             for (let track of this.score.tracks) {
@@ -157,7 +159,12 @@ export class MasterBar {
      * @param fermata The fermata.
      */
     public addFermata(offset: number, fermata: Fermata): void {
-        this.fermata.set(offset, fermata);
+        let fermataMap = this.fermata;
+        if (fermataMap === null) {
+            fermataMap = new Map<number, Fermata>();
+            this.fermata = fermataMap;
+        }
+        fermataMap.set(offset, fermata);
     }
 
     /**
@@ -166,8 +173,12 @@ export class MasterBar {
      * @returns
      */
     public getFermata(beat: Beat): Fermata | null {
-        if (this.fermata.has(beat.playbackStart)) {
-            return this.fermata.get(beat.playbackStart)!;
+        const fermataMap = this.fermata;
+        if (fermataMap === null) {
+            return null;
+        }
+        if (fermataMap.has(beat.playbackStart)) {
+            return fermataMap.get(beat.playbackStart)!;
         }
         return null;
     }
diff --git a/src/model/Note.ts b/src/model/Note.ts
index b636d8ab4..bc2a65b38 100644
--- a/src/model/Note.ts
+++ b/src/model/Note.ts
@@ -21,12 +21,22 @@ import { ModelUtils } from '@src/model/ModelUtils';
 import { PickStroke } from '@src/model/PickStroke';
 import { PercussionMapper } from '@src/model/PercussionMapper';
 
+class NoteIdBag {
+    public tieDestinationNoteId: number = -1;
+    public tieOriginNoteId: number = -1;
+    public slurDestinationNoteId: number = -1;
+    public slurOriginNoteId: number = -1;
+    public hammerPullDestinationNoteId: number = -1;
+    public hammerPullOriginNoteId: number = -1;
+}
+
 /**
  * A note is a single played sound on a fretted instrument.
  * It consists of a fret offset and a string on which the note is played on.
  * It also can be modified by a lot of different effects.
  * @cloneable
  * @json
+ * @json_strict
  */
 export class Note {
     public static GlobalNoteId: number = 0;
@@ -74,7 +84,7 @@ export class Note {
      * @clone_add addBendPoint
      * @json_add addBendPoint
      */
-    public bendPoints: BendPoint[] = [];
+    public bendPoints: BendPoint[] | null = null;
 
     /**
      * Gets or sets the bend point with the highest bend value.
@@ -84,7 +94,7 @@ export class Note {
     public maxBendPoint: BendPoint | null = null;
 
     public get hasBend(): boolean {
-        return this.bendType !== BendType.None;
+        return this.bendPoints !== null && this.bendType !== BendType.None;
     }
 
     public get isStringed(): boolean {
@@ -257,29 +267,19 @@ export class Note {
         return !!this.hammerPullOrigin;
     }
 
-    /**
-     * Gets the origin note id of the hammeron/pull-off of this note.
-     */
-    public hammerPullOriginNoteId: number = -1;
-
     /**
      * Gets the origin of the hammeron/pulloff of this note.
+     * @clone_ignore
+     * @json_ignore
      */
-    public get hammerPullOrigin(): Note | null {
-        return this.hammerPullOriginNoteId === -1 ? null : this.beat.voice.bar.staff.track.score.getNoteById(this.hammerPullOriginNoteId);
-    }
-
-    /**
-     * Gets the destination note id of the hammeron/pull-off of this note.
-     */
-    public hammerPullDestinationNoteId: number = -1;
+    public hammerPullOrigin: Note | null = null;
 
     /**
      * Gets the destination for the hammeron/pullof started by this note.
+     * @clone_ignore
+     * @json_ignore
      */
-    public get hammerPullDestination(): Note | null {
-        return this.hammerPullDestinationNoteId === -1 ? null : this.beat.voice.bar.staff.track.score.getNoteById(this.hammerPullDestinationNoteId);
-    }
+    public hammerPullDestination: Note | null = null;
 
     public get isSlurOrigin(): boolean {
         return !!this.slurDestination;
@@ -290,30 +290,19 @@ export class Note {
      */
     public isSlurDestination: boolean = false;
 
-
-    /**
-     * Gets the note id where the slur of this note starts.
-     */
-    public slurOriginNoteId: number = -1;
-
     /**
      * Gets or sets the note where the slur of this note starts.
+     * @clone_ignore
+     * @json_ignore
      */
-    public get slurOrigin(): Note | null {
-        return this.slurOriginNoteId === -1 ? null : this.beat.voice.bar.staff.track.score.getNoteById(this.slurOriginNoteId);
-    }
-
-    /**
-     * Gets or sets the note id where the slur of this note ends.
-     */
-    public slurDestinationNoteId: number = -1;
+    public slurOrigin: Note | null = null;
 
     /**
      * Gets or sets the note where the slur of this note ends.
+     * @clone_ignore
+     * @json_ignore
      */
-    public get slurDestination(): Note | null {
-        return this.slurDestinationNoteId === -1 ? null : this.beat.voice.bar.staff.track.score.getNoteById(this.slurDestinationNoteId);
-    }
+    public slurDestination: Note | null = null;
 
     public get isHarmonic(): boolean {
         return this.harmonicType !== HarmonicType.None;
@@ -397,30 +386,19 @@ export class Note {
      */
     public vibrato: VibratoType = VibratoType.None;
 
-
-    /**
-     * Gets the origin note id of the tied if this note is tied.
-     */
-    public tieOriginNoteId: number = -1;
-
     /**
      * Gets the origin of the tied if this note is tied.
+     * @clone_ignore
+     * @json_ignore
      */
-    public get tieOrigin(): Note | null {
-        return this.tieOriginNoteId === -1 ? null : this.beat.voice.bar.staff.track.score.getNoteById(this.tieOriginNoteId);
-    }
-
-    /**
-     * Gets the desination note id of the tie.
-     */
-    public tieDestinationNoteId: number = -1;
+    public tieOrigin: Note | null = null;
 
     /**
      * Gets the desination of the tie.
+     * @clone_ignore
+     * @json_ignore 
      */
-    public get tieDestination(): Note | null {
-        return this.tieDestinationNoteId === -1 ? null : this.beat.voice.bar.staff.track.score.getNoteById(this.tieDestinationNoteId);
-    }
+    public tieDestination: Note | null = null;
 
     /**
      * Gets or sets whether this note is ends a tied note.
@@ -428,7 +406,7 @@ export class Note {
     public isTieDestination: boolean = false;
 
     public get isTieOrigin(): boolean {
-        return this.tieDestinationNoteId !== -1;
+        return this.tieDestination !== null;
     }
 
     /**
@@ -632,15 +610,15 @@ export class Note {
 
     public get initialBendValue(): number {
         if (this.hasBend) {
-            return Math.floor(this.bendPoints[0].value / 2);
+            return Math.floor(this.bendPoints![0].value / 2);
         } else if (this.bendOrigin) {
-            return Math.floor(this.bendOrigin.bendPoints[this.bendOrigin.bendPoints.length - 1].value / 2);
+            return Math.floor(this.bendOrigin.bendPoints![this.bendOrigin.bendPoints!.length - 1].value / 2);
         } else if (this.isTieDestination && this.tieOrigin!.bendOrigin) {
-            return Math.floor(this.tieOrigin!.bendOrigin.bendPoints[this.tieOrigin!.bendOrigin.bendPoints.length - 1].value / 2) ;
+            return Math.floor(this.tieOrigin!.bendOrigin.bendPoints![this.tieOrigin!.bendOrigin.bendPoints!.length - 1].value / 2);
         } else if (this.beat.hasWhammyBar) {
-            return Math.floor(this.beat.whammyBarPoints[0].value / 2);
+            return Math.floor(this.beat.whammyBarPoints![0].value / 2);
         } else if (this.beat.isContinuedWhammy) {
-            return Math.floor(this.beat.previousBeat!.whammyBarPoints[this.beat.previousBeat!.whammyBarPoints.length - 1].value / 2);
+            return Math.floor(this.beat.previousBeat!.whammyBarPoints![this.beat.previousBeat!.whammyBarPoints!.length - 1].value / 2);
         }
         return 0;
     }
@@ -691,17 +669,17 @@ export class Note {
 
     public get hasQuarterToneOffset(): boolean {
         if (this.hasBend) {
-            return this.bendPoints[0].value % 2 !== 0;
+            return this.bendPoints![0].value % 2 !== 0;
         }
         if (this.bendOrigin) {
-            return this.bendOrigin.bendPoints[this.bendOrigin.bendPoints.length - 1].value % 2 !== 0;
+            return this.bendOrigin.bendPoints![this.bendOrigin.bendPoints!.length - 1].value % 2 !== 0;
         }
         if (this.beat.hasWhammyBar) {
-            return this.beat.whammyBarPoints[0].value % 2 !== 0;
+            return this.beat.whammyBarPoints![0].value % 2 !== 0;
         }
         if (this.beat.isContinuedWhammy) {
             return (
-                this.beat.previousBeat!.whammyBarPoints[this.beat.previousBeat!.whammyBarPoints.length - 1].value %
+                this.beat.previousBeat!.whammyBarPoints![this.beat.previousBeat!.whammyBarPoints!.length - 1].value %
                 2 !==
                 0
             );
@@ -710,7 +688,12 @@ export class Note {
     }
 
     public addBendPoint(point: BendPoint): void {
-        this.bendPoints.push(point);
+        let points = this.bendPoints;
+        if (points === null) {
+            points = [];
+            this.bendPoints = points;
+        }
+        points.push(point);
         if (!this.maxBendPoint || point.value > this.maxBendPoint.value) {
             this.maxBendPoint = point;
         }
@@ -719,13 +702,13 @@ export class Note {
         }
     }
 
-    public finish(settings: Settings): void {
+    public finish(settings: Settings, sharedDataBag: Map<string, unknown>): void {
         let nextNoteOnLine: Lazy<Note | null> = new Lazy<Note | null>(() => Note.nextNoteOnSameLine(this));
         let isSongBook: boolean = settings && settings.notation.notationMode === NotationMode.SongBook;
 
         // connect ties
         if (this.isTieDestination) {
-            this.chain();
+            this.chain(sharedDataBag);
             // implicit let ring
             if (isSongBook && this.tieOrigin && this.tieOrigin.isLetRing) {
                 this.isLetRing = true;
@@ -756,8 +739,8 @@ export class Note {
             if (!hammerPullDestination) {
                 this.isHammerPullOrigin = false;
             } else {
-                this.hammerPullDestinationNoteId = hammerPullDestination.id;
-                hammerPullDestination.hammerPullOriginNoteId = this.id;
+                this.hammerPullDestination = hammerPullDestination;
+                hammerPullDestination.hammerPullOrigin = this;
             }
         }
         // set slides
@@ -792,14 +775,15 @@ export class Note {
         }
         // try to detect what kind of bend was used and cleans unneeded points if required
         // Guitar Pro 6 and above (gpif.xml) uses exactly 4 points to define all bends
-        if (this.bendPoints.length > 0 && this.bendType === BendType.Custom) {
+        const points = this.bendPoints;
+        if (points != null && points.length > 0 && this.bendType === BendType.Custom) {
             let isContinuedBend: boolean = this.isTieDestination && this.tieOrigin!.hasBend;
             this.isContinuedBend = isContinuedBend;
-            if (this.bendPoints.length === 4) {
-                let origin: BendPoint = this.bendPoints[0];
-                let middle1: BendPoint = this.bendPoints[1];
-                let middle2: BendPoint = this.bendPoints[2];
-                let destination: BendPoint = this.bendPoints[3];
+            if (points.length === 4) {
+                let origin: BendPoint = points[0];
+                let middle1: BendPoint = points[1];
+                let middle2: BendPoint = points[2];
+                let destination: BendPoint = points[3];
                 // the middle points are used for holds, anything else is a new feature we do not support yet
                 if (middle1.value === middle2.value) {
                     // bend higher?
@@ -808,43 +792,43 @@ export class Note {
                             this.bendType = BendType.BendRelease;
                         } else if (!isContinuedBend && origin.value > 0) {
                             this.bendType = BendType.PrebendBend;
-                            this.bendPoints.splice(2, 1);
-                            this.bendPoints.splice(1, 1);
+                            points.splice(2, 1);
+                            points.splice(1, 1);
                         } else {
                             this.bendType = BendType.Bend;
-                            this.bendPoints.splice(2, 1);
-                            this.bendPoints.splice(1, 1);
+                            points.splice(2, 1);
+                            points.splice(1, 1);
                         }
                     } else if (destination.value < origin.value) {
                         // origin must be > 0 otherwise it's no release, we cannot bend negative
                         if (isContinuedBend) {
                             this.bendType = BendType.Release;
-                            this.bendPoints.splice(2, 1);
-                            this.bendPoints.splice(1, 1);
+                            points.splice(2, 1);
+                            points.splice(1, 1);
                         } else {
                             this.bendType = BendType.PrebendRelease;
-                            this.bendPoints.splice(2, 1);
-                            this.bendPoints.splice(1, 1);
+                            points.splice(2, 1);
+                            points.splice(1, 1);
                         }
                     } else {
                         if (middle1.value > origin.value) {
                             this.bendType = BendType.BendRelease;
                         } else if (origin.value > 0 && !isContinuedBend) {
                             this.bendType = BendType.Prebend;
-                            this.bendPoints.splice(2, 1);
-                            this.bendPoints.splice(1, 1);
+                            points.splice(2, 1);
+                            points.splice(1, 1);
                         } else {
                             this.bendType = BendType.Hold;
-                            this.bendPoints.splice(2, 1);
-                            this.bendPoints.splice(1, 1);
+                            points.splice(2, 1);
+                            points.splice(1, 1);
                         }
                     }
                 } else {
                     Logger.warning('Model', 'Unsupported bend type detected, fallback to custom', null);
                 }
-            } else if (this.bendPoints.length === 2) {
-                let origin: BendPoint = this.bendPoints[0];
-                let destination: BendPoint = this.bendPoints[1];
+            } else if (points.length === 2) {
+                let origin: BendPoint = points[0];
+                let destination: BendPoint = points[1];
                 // bend higher?
                 if (destination.value > origin.value) {
                     if (!isContinuedBend && origin.value > 0) {
@@ -863,7 +847,7 @@ export class Note {
                     this.bendType = BendType.Hold;
                 }
             }
-        } else if (this.bendPoints.length === 0) {
+        } else if (points === null || points.length === 0) {
             this.bendType = BendType.None;
         }
 
@@ -969,30 +953,136 @@ export class Note {
         return null;
     }
 
-    public chain() {
-        this.beat.voice.bar.staff.track.score.registerNote(this);
-        if (!this.isTieDestination) {
-            return;
-        }
+    private static NoteIdLookupKey = "NoteIdLookup";
+
+    private _noteIdBag: NoteIdBag | null = null;
+    public chain(sharedDataBag: Map<string, unknown>) {
+        // if we have some IDs from a serialization flow, 
+        // we need to lookup/register the notes correctly
+        if (this._noteIdBag != null) {
+            // get or create lookup
+            let noteIdLookup: Map<number, Note>;
+            if (sharedDataBag.has(Note.NoteIdLookupKey)) {
+                noteIdLookup = sharedDataBag.get(Note.NoteIdLookupKey) as Map<number, Note>;
+            } else {
+                noteIdLookup = new Map<number, Note>();
+                sharedDataBag.set(Note.NoteIdLookupKey, noteIdLookup);
+            }
 
-        let tieOrigin: Note | null;
-        if (this.tieOriginNoteId === -1) {
-            tieOrigin = Note.findTieOrigin(this);
-            this.tieOriginNoteId = tieOrigin ? tieOrigin.id : -1;
-        } else {
-            tieOrigin = this.tieOrigin;
-        }
+            // if this note is a source note for any effect, remember it for later
+            // the destination note will look it up for linking
+            if (this._noteIdBag.hammerPullDestinationNoteId !== -1 ||
+                this._noteIdBag.tieDestinationNoteId !== -1 ||
+                this._noteIdBag.slurDestinationNoteId !== -1) {
+                noteIdLookup.set(this.id, this);
+            }
+
+            // on any effect destiniation, lookup the origin which should already be 
+            // registered
+            if (this._noteIdBag.hammerPullOriginNoteId !== -1) {
+                this.hammerPullOrigin = noteIdLookup.get(this._noteIdBag.hammerPullOriginNoteId)!;
+                this.hammerPullOrigin.hammerPullDestination = this;
+            }
+            if (this._noteIdBag.tieOriginNoteId !== -1) {
+                this.tieOrigin = noteIdLookup.get(this._noteIdBag.tieOriginNoteId)!;
+                this.tieOrigin.tieDestination = this;
+            }
+            if (this._noteIdBag.slurOriginNoteId !== -1) {
+                this.slurOrigin = noteIdLookup.get(this._noteIdBag.slurOriginNoteId)!;
+                this.slurOrigin.slurDestination = this;
+            }
 
-        if (!tieOrigin) {
-            this.isTieDestination = false;
+            this._noteIdBag = null; // not needed anymore
         } else {
-            tieOrigin.tieDestinationNoteId = this.id;
-            this.fret = tieOrigin.fret;
-            this.octave = tieOrigin.octave;
-            this.tone = tieOrigin.tone;
-            if (tieOrigin.hasBend) {
-                this.bendOrigin = this.tieOrigin;
+            if (!this.isTieDestination && this.tieOrigin == null) {
+                return;
+            }
+
+            let tieOrigin = Note.findTieOrigin(this);
+            if (!tieOrigin) {
+                this.isTieDestination = false;
+            } else {
+                tieOrigin.tieDestination = this;
+                this.tieOrigin = tieOrigin;
+                this.fret = tieOrigin.fret;
+                this.octave = tieOrigin.octave;
+                this.tone = tieOrigin.tone;
+                if (tieOrigin.hasBend) {
+                    this.bendOrigin = this.tieOrigin;
+                }
             }
         }
     }
+
+    /**
+     * @internal
+     */
+    public toJson(o: Map<string, unknown>) {
+        // inject linked note ids into JSON
+        if (this.tieDestination !== null) {
+            o.set("tiedestinationnoteid", this.tieDestination.id)
+        }
+        if (this.tieOrigin !== null) {
+            o.set("tieoriginnoteid", this.tieOrigin.id)
+        }
+        if (this.slurDestination !== null) {
+            o.set("slurdestinationnoteid", this.slurDestination.id)
+        }
+        if (this.slurOrigin !== null) {
+            o.set("sluroriginnoteid", this.slurOrigin.id)
+        }
+        if (this.hammerPullOrigin !== null) {
+            o.set("hammerpulloriginnoteid", this.hammerPullOrigin.id)
+        }
+        if (this.hammerPullDestination !== null) {
+            o.set("hammerpulldestinationnoteid", this.hammerPullDestination.id)
+        }
+    }
+
+    /**
+     * @internal
+     */
+    public setProperty(property: string, v: unknown): boolean {
+        switch (property) {
+            case "tiedestinationnoteid":
+                if (this._noteIdBag == null) {
+                    this._noteIdBag = new NoteIdBag();
+                }
+                this._noteIdBag.tieDestinationNoteId = v as number;
+                return true;
+            case "tieoriginnoteid":
+                if (this._noteIdBag == null) {
+                    this._noteIdBag = new NoteIdBag();
+                }
+                this._noteIdBag.tieOriginNoteId = v as number;
+                return true;
+
+            case "slurdestinationnoteid":
+                if (this._noteIdBag == null) {
+                    this._noteIdBag = new NoteIdBag();
+                }
+                this._noteIdBag.slurDestinationNoteId = v as number;
+                return true;
+            case "sluroriginnoteid":
+                if (this._noteIdBag == null) {
+                    this._noteIdBag = new NoteIdBag();
+                }
+                this._noteIdBag.slurOriginNoteId = v as number;
+                return true;
+
+            case "hammerpulloriginnoteid":
+                if (this._noteIdBag == null) {
+                    this._noteIdBag = new NoteIdBag();
+                }
+                this._noteIdBag.hammerPullOriginNoteId = v as number;
+                return true;
+            case "hammerpulldestinationnoteid":
+                if (this._noteIdBag == null) {
+                    this._noteIdBag = new NoteIdBag();
+                }
+                this._noteIdBag.hammerPullDestinationNoteId = v as number;
+                return true;
+        }
+        return false;
+    }
 }
diff --git a/src/model/PlaybackInformation.ts b/src/model/PlaybackInformation.ts
index 696b09a55..a03f162f2 100644
--- a/src/model/PlaybackInformation.ts
+++ b/src/model/PlaybackInformation.ts
@@ -2,6 +2,7 @@
  * This public class stores the midi specific information of a track needed
  * for playback.
  * @json
+ * @json_strict
  */
 export class PlaybackInformation {
     /**
diff --git a/src/model/RenderStylesheet.ts b/src/model/RenderStylesheet.ts
index 445a56355..7470197e7 100644
--- a/src/model/RenderStylesheet.ts
+++ b/src/model/RenderStylesheet.ts
@@ -2,6 +2,7 @@
  * This class represents the rendering stylesheet.
  * It contains settings which control the display of the score when rendered.
  * @json
+ * @json_strict
  */
 export class RenderStylesheet {
     /**
diff --git a/src/model/Score.ts b/src/model/Score.ts
index 4a9bcc18e..6cd580e2a 100644
--- a/src/model/Score.ts
+++ b/src/model/Score.ts
@@ -3,16 +3,15 @@ import { RenderStylesheet } from '@src/model/RenderStylesheet';
 import { RepeatGroup } from '@src/model/RepeatGroup';
 import { Track } from '@src/model/Track';
 import { Settings } from '@src/Settings';
-import { Note } from '@src/model/Note';
 
 /**
  * The score is the root node of the complete
  * model. It stores the basic information of
  * a song and stores the sub components.
  * @json
+ * @json_strict
  */
 export class Score {
-    private _noteByIdLookup: Map<number, Note> = new Map<number, Note>();
     private _currentRepeatGroup: RepeatGroup = new RepeatGroup();
 
     /**
@@ -130,20 +129,9 @@ export class Score {
     }
 
     public finish(settings: Settings): void {
-        this._noteByIdLookup.clear();
-
+        const sharedDataBag = new Map<string, unknown>()
         for (let i: number = 0, j: number = this.tracks.length; i < j; i++) {
-            this.tracks[i].finish(settings);
+            this.tracks[i].finish(settings, sharedDataBag);
         }
     }
-
-    public registerNote(note: Note) {
-        this._noteByIdLookup.set(note.id, note);
-    }
-
-    public getNoteById(noteId: number): Note | null {
-        return this._noteByIdLookup.has(noteId)
-            ? this._noteByIdLookup.get(noteId)!
-            : null;
-    }
 }
diff --git a/src/model/Section.ts b/src/model/Section.ts
index cc1fb3821..0897874c6 100644
--- a/src/model/Section.ts
+++ b/src/model/Section.ts
@@ -2,6 +2,7 @@
  * This public class is used to describe the beginning of a
  * section within a song. It acts like a marker.
  * @json
+ * @json_strict
  */
 export class Section {
     /**
diff --git a/src/model/Staff.ts b/src/model/Staff.ts
index 058a1e9dc..40a2e72a8 100644
--- a/src/model/Staff.ts
+++ b/src/model/Staff.ts
@@ -8,6 +8,7 @@ import { Tuning } from '@src/model/Tuning';
  * This class describes a single staff within a track. There are instruments like pianos
  * where a single track can contain multiple staffs.
  * @json
+ * @json_strict
  */
 export class Staff {
     /**
@@ -32,7 +33,7 @@ export class Staff {
      * Gets or sets a list of all chords defined for this staff. {@link Beat.chordId} refers to entries in this lookup.
      * @json_add addChord
      */
-    public chords: Map<string, Chord> = new Map<string, Chord>();
+    public chords: Map<string, Chord> | null = null;
 
     /**
      * Gets or sets the fret on which a capo is set.
@@ -97,17 +98,31 @@ export class Staff {
      */
     public standardNotationLineCount: number = 5;
 
-    public finish(settings: Settings): void {
+    public finish(settings: Settings, sharedDataBag: Map<string, unknown>): void {
         this.stringTuning.finish();
         for (let i: number = 0, j: number = this.bars.length; i < j; i++) {
-            this.bars[i].finish(settings);
+            this.bars[i].finish(settings, sharedDataBag);
         }
     }
 
     public addChord(chordId: string, chord: Chord): void {
         chord.staff = this;
-        this.chords.set(chordId, chord);
+        let chordMap = this.chords;
+        if (chordMap === null) {
+            chordMap = new Map<string, Chord>();
+            this.chords = chordMap;
+        }
+        chordMap.set(chordId, chord);
+    }
+
+    public hasChord(chordId: string): boolean {
+        return this.chords?.has(chordId) ?? false
+    }
+
+    public getChord(chordId: string): Chord | null {
+        return this.chords?.get(chordId) ?? null
     }
+    
 
     public addBar(bar: Bar): void {
         let bars: Bar[] = this.bars;
diff --git a/src/model/Track.ts b/src/model/Track.ts
index 4f7f83c30..3005d7b49 100644
--- a/src/model/Track.ts
+++ b/src/model/Track.ts
@@ -11,6 +11,7 @@ import { InstrumentArticulation } from '@src/model/InstrumentArticulation';
  * This public class describes a single track or instrument of score.
  * It is bascially a list of staffs containing individual music notation kinds.
  * @json
+ * @json_strict
  */
 export class Track {
     private static readonly ShortNameMaxLength: number = 10;
@@ -70,7 +71,7 @@ export class Track {
         this.staves.push(staff);
     }
 
-    public finish(settings: Settings): void {
+    public finish(settings: Settings, sharedDataBag: Map<string, unknown>): void {
         if (!this.shortName) {
             this.shortName = this.name;
             if (this.shortName.length > Track.ShortNameMaxLength) {
@@ -78,7 +79,7 @@ export class Track {
             }
         }
         for (let i: number = 0, j: number = this.staves.length; i < j; i++) {
-            this.staves[i].finish(settings);
+            this.staves[i].finish(settings, sharedDataBag);
         }
     }
 
diff --git a/src/model/Tuning.ts b/src/model/Tuning.ts
index 970b2a8d1..ff8e70896 100644
--- a/src/model/Tuning.ts
+++ b/src/model/Tuning.ts
@@ -1,6 +1,7 @@
 /**
  * This public class represents a predefined string tuning.
  * @json
+ * @json_strict
  */
 export class Tuning {
     private static _sevenStrings: Tuning[] = [];
diff --git a/src/model/Voice.ts b/src/model/Voice.ts
index fa41a0efd..26ca5ea7a 100644
--- a/src/model/Voice.ts
+++ b/src/model/Voice.ts
@@ -8,6 +8,7 @@ import { GraceGroup } from '@src/model/GraceGroup';
  * A voice represents a group of beats
  * that can be played during a bar.
  * @json
+ * @json_strict
  */
 export class Voice {
     private _beatLookup!: Map<number, Beat>;
@@ -62,7 +63,7 @@ export class Voice {
         }
     }
 
-    private chain(beat: Beat): void {
+    private chain(beat: Beat, sharedDataBag: Map<string, unknown>): void {
         if (!this.bar) {
             return;
         }
@@ -79,7 +80,7 @@ export class Voice {
             }
         }
 
-        beat.chain();
+        beat.chain(sharedDataBag);
     }
 
     public addGraceBeat(beat: Beat): void {
@@ -104,13 +105,13 @@ export class Voice {
         return null;
     }
 
-    public finish(settings: Settings): void {
+    public finish(settings: Settings, sharedDataBag: Map<string, unknown>): void {
         this._beatLookup = new Map<number, Beat>();
         let currentGraceGroup: GraceGroup | null = null;
         for (let index: number = 0; index < this.beats.length; index++) {
             let beat: Beat = this.beats[index];
             beat.index = index;
-            this.chain(beat);
+            this.chain(beat, sharedDataBag);
             if (beat.graceType === GraceType.None) {
                 beat.graceGroup = currentGraceGroup;
                 if (currentGraceGroup) {
@@ -130,7 +131,7 @@ export class Voice {
         for (let i: number = 0; i < this.beats.length; i++) {
             let beat: Beat = this.beats[i];
             beat.index = i;
-            beat.finish(settings);
+            beat.finish(settings, sharedDataBag);
 
             // if this beat is a non-grace but has grace notes
             // we need to first steal the duration from the right beat
diff --git a/src/platform/javascript/AlphaSynthAudioWorkletOutput.ts b/src/platform/javascript/AlphaSynthAudioWorkletOutput.ts
index 5e2699acf..949605229 100644
--- a/src/platform/javascript/AlphaSynthAudioWorkletOutput.ts
+++ b/src/platform/javascript/AlphaSynthAudioWorkletOutput.ts
@@ -88,7 +88,7 @@ export class AlphaSynthWebWorklet {
                     }
                 }
 
-                public process(
+                public override process(
                     _inputs: Float32Array[][],
                     outputs: Float32Array[][],
                     _parameters: Record<string, Float32Array>
@@ -158,12 +158,12 @@ export class AlphaSynthWebWorklet {
 export class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
     private _worklet: AudioWorkletNode | null = null;
 
-    public open() {
+    public override open() {
         super.open();
         this.onReady();
     }
 
-    public play(): void {
+    public override play(): void {
         super.play();
         let ctx = this._context!;
         // create a script processor node which will replace the silence with the generated audio
@@ -198,7 +198,7 @@ export class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
         }
     }
 
-    public pause(): void {
+    public override pause(): void {
         super.pause();
         if (this._worklet) {
             this._worklet.port.onmessage = null;
diff --git a/src/platform/javascript/AlphaSynthScriptProcessorOutput.ts b/src/platform/javascript/AlphaSynthScriptProcessorOutput.ts
index 7b57e973d..1888909e5 100644
--- a/src/platform/javascript/AlphaSynthScriptProcessorOutput.ts
+++ b/src/platform/javascript/AlphaSynthScriptProcessorOutput.ts
@@ -14,7 +14,7 @@ export class AlphaSynthScriptProcessorOutput extends AlphaSynthWebAudioOutputBas
     private _bufferCount: number = 0;
     private _requestedBufferCount: number = 0;
 
-    public open() {
+    public override open() {
         super.open();
         this._bufferCount = Math.floor(
             (AlphaSynthWebAudioOutputBase.TotalBufferTimeInMilliseconds * this.sampleRate) /
@@ -25,7 +25,7 @@ export class AlphaSynthScriptProcessorOutput extends AlphaSynthWebAudioOutputBas
         this.onReady();
     }
 
-    public play(): void {
+    public override play(): void {
         super.play();
         let ctx = this._context!;
         // create a script processor node which will replace the silence with the generated audio
@@ -41,7 +41,7 @@ export class AlphaSynthScriptProcessorOutput extends AlphaSynthWebAudioOutputBas
         this._audioNode.connect(ctx.destination, 0, 0);
     }
 
-    public pause(): void {
+    public override pause(): void {
         super.pause();
         if (this._audioNode) {
             this._audioNode.disconnect(0);
diff --git a/src/platform/javascript/AlphaTabApi.ts b/src/platform/javascript/AlphaTabApi.ts
index ebf186cab..8a9adaeae 100644
--- a/src/platform/javascript/AlphaTabApi.ts
+++ b/src/platform/javascript/AlphaTabApi.ts
@@ -20,7 +20,7 @@ export class AlphaTabApi extends AlphaTabApiBase<unknown> {
         super(new BrowserUiFacade(element), options);
     }
 
-    public tex(tex: string, tracks?: number[]): void {
+    public override tex(tex: string, tracks?: number[]): void {
         let browser: BrowserUiFacade = this.uiFacade as BrowserUiFacade;
         super.tex(tex, browser.parseTracks(tracks));
     }
@@ -104,17 +104,17 @@ export class AlphaTabApi extends AlphaTabApiBase<unknown> {
         document.body.removeChild(dlLink);
     }
 
-    public changeTrackMute(tracks: Track[], mute: boolean): void {
+    public override changeTrackMute(tracks: Track[], mute: boolean): void {
         let trackList: Track[] = this.trackIndexesToTracks((this.uiFacade as BrowserUiFacade).parseTracks(tracks));
         super.changeTrackMute(trackList, mute);
     }
 
-    public changeTrackSolo(tracks: Track[], solo: boolean): void {
+    public override changeTrackSolo(tracks: Track[], solo: boolean): void {
         let trackList: Track[] = this.trackIndexesToTracks((this.uiFacade as BrowserUiFacade).parseTracks(tracks));
         super.changeTrackSolo(trackList, solo);
     }
 
-    public changeTrackVolume(tracks: Track[], volume: number): void {
+    public override changeTrackVolume(tracks: Track[], volume: number): void {
         let trackList: Track[] = this.trackIndexesToTracks((this.uiFacade as BrowserUiFacade).parseTracks(tracks));
         super.changeTrackVolume(trackList, volume);
     }
diff --git a/src/platform/javascript/AlphaTabWebWorker.ts b/src/platform/javascript/AlphaTabWebWorker.ts
index ba6832dd6..66aa94b55 100644
--- a/src/platform/javascript/AlphaTabWebWorker.ts
+++ b/src/platform/javascript/AlphaTabWebWorker.ts
@@ -80,7 +80,7 @@ export class AlphaTabWebWorker {
                 break;
             case 'alphaTab.renderScore':
                 this.updateFontSizes(data.fontSizes);
-                let score: any = JsonConverter.jsObjectToScore(data.score, this._renderer.settings);
+                let score: any = data.score == null ? null : JsonConverter.jsObjectToScore(data.score, this._renderer.settings);
                 this.renderMultiple(score, data.trackIndexes);
                 break;
             case 'alphaTab.updateSettings':
@@ -104,7 +104,7 @@ export class AlphaTabWebWorker {
         SettingsSerializer.fromJson(this._renderer.settings, json);
     }
 
-    private renderMultiple(score: Score, trackIndexes: number[]): void {
+    private renderMultiple(score: Score | null, trackIndexes: number[] | null): void {
         try {
             this._renderer.renderScore(score, trackIndexes);
         } catch (e) {
diff --git a/src/platform/javascript/AlphaTabWorkerScoreRenderer.ts b/src/platform/javascript/AlphaTabWorkerScoreRenderer.ts
index 5f98d5fbc..d28f656fa 100644
--- a/src/platform/javascript/AlphaTabWorkerScoreRenderer.ts
+++ b/src/platform/javascript/AlphaTabWorkerScoreRenderer.ts
@@ -122,8 +122,8 @@ export class AlphaTabWorkerScoreRenderer<T> implements IScoreRenderer {
         }
     }
 
-    public renderScore(score: Score, trackIndexes: number[]): void {
-        let jsObject: unknown = JsonConverter.scoreToJsObject(score);
+    public renderScore(score: Score | null, trackIndexes: number[] | null): void {
+        let jsObject: unknown = score == null ? null : JsonConverter.scoreToJsObject(score);
         this._worker.postMessage({
             cmd: 'alphaTab.renderScore',
             score: jsObject,
diff --git a/src/rendering/BarRendererBase.ts b/src/rendering/BarRendererBase.ts
index 53f2ac6be..d77acac84 100644
--- a/src/rendering/BarRendererBase.ts
+++ b/src/rendering/BarRendererBase.ts
@@ -311,7 +311,7 @@ export class BarRendererBase {
     }
 
     protected getVoiceContainer(voice: Voice): VoiceContainerGlyph | undefined {
-        return this._voiceContainers.get(voice.index);
+        return this._voiceContainers.has(voice.index) ? this._voiceContainers.get(voice.index) : undefined;
     }
 
     public getBeatContainer(beat: Beat): BeatContainerGlyph | undefined {
diff --git a/src/rendering/EffectBand.ts b/src/rendering/EffectBand.ts
index d0a6f9401..001708dd0 100644
--- a/src/rendering/EffectBand.ts
+++ b/src/rendering/EffectBand.ts
@@ -18,7 +18,7 @@ export class EffectBand extends Glyph {
     public isLinkedToPrevious: boolean = false;
     public firstBeat: Beat | null = null;
     public lastBeat: Beat | null = null;
-    public height: number = 0;
+    public override height: number = 0;
     public voice: Voice;
     public info: EffectBarRendererInfo;
     public slot: EffectBandSlot | null = null;
@@ -29,7 +29,7 @@ export class EffectBand extends Glyph {
         this.info = info;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         for (let i: number = 0; i < this.renderer.bar.voices.length; i++) {
             this._effectGlyphs.push(new Map<number, EffectGlyph>());
@@ -139,7 +139,7 @@ export class EffectBand extends Glyph {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx, cy, canvas);
         // canvas.LineWidth = 1;
         // canvas.StrokeRect(cx + X, cy + Y, Renderer.Width, Slot.Shared.Height);
diff --git a/src/rendering/EffectBarRenderer.ts b/src/rendering/EffectBarRenderer.ts
index 479341040..10e028792 100644
--- a/src/rendering/EffectBarRenderer.ts
+++ b/src/rendering/EffectBarRenderer.ts
@@ -25,7 +25,7 @@ export class EffectBarRenderer extends BarRendererBase {
         this._infos = infos;
     }
 
-    protected updateSizes(): void {
+    protected override updateSizes(): void {
         this.topOverflow = 0;
         this.bottomOverflow = 0;
         this.topPadding = 0;
@@ -34,7 +34,7 @@ export class EffectBarRenderer extends BarRendererBase {
         super.updateSizes();
     }
 
-    public finalizeRenderer(): void {
+    public override finalizeRenderer(): void {
         super.finalizeRenderer();
         this.updateHeight();
     }
@@ -55,7 +55,7 @@ export class EffectBarRenderer extends BarRendererBase {
         this.height = y;
     }
 
-    public applyLayoutingInfo(): boolean {
+    public override applyLayoutingInfo(): boolean {
         if (!super.applyLayoutingInfo()) {
             return false;
         }
@@ -77,14 +77,14 @@ export class EffectBarRenderer extends BarRendererBase {
         return true;
     }
 
-    public scaleToWidth(width: number): void {
+    public override scaleToWidth(width: number): void {
         super.scaleToWidth(width);
         for (let effectBand of this._bands) {
             effectBand.alignGlyphs();
         }
     }
 
-    protected createBeatGlyphs(): void {
+    protected override createBeatGlyphs(): void {
         this._bands = [];
         this._bandLookup = new Map<string, EffectBand>();
         for (let voice of this.bar.voices) {
@@ -110,7 +110,7 @@ export class EffectBarRenderer extends BarRendererBase {
         }
     }
 
-    protected createVoiceGlyphs(v: Voice): void {
+    protected override createVoiceGlyphs(v: Voice): void {
         for (let b of v.beats) {
             // we create empty glyphs as alignment references and to get the
             // effect bar sized
@@ -124,7 +124,7 @@ export class EffectBarRenderer extends BarRendererBase {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         this.paintBackground(cx, cy, canvas);
         // canvas.color = new Color(255, 0, 0, 100);
         // canvas.fillRect(cx + this.x, cy + this.y, this.width, this.height);
diff --git a/src/rendering/IScoreRenderer.ts b/src/rendering/IScoreRenderer.ts
index 8fd9711ba..b220d5917 100644
--- a/src/rendering/IScoreRenderer.ts
+++ b/src/rendering/IScoreRenderer.ts
@@ -33,7 +33,7 @@ export interface IScoreRenderer {
      * @param score The score defining the tracks.
      * @param trackIndexes The indexes of the tracks to draw.
      */
-    renderScore(score: Score, trackIndexes: number[]): void;
+    renderScore(score: Score | null, trackIndexes: number[] | null): void;
 
     /**
      * Initiates the rendering of a partial render result which the renderer
diff --git a/src/rendering/ScoreBarRenderer.ts b/src/rendering/ScoreBarRenderer.ts
index 3084eb9fe..bc397493a 100644
--- a/src/rendering/ScoreBarRenderer.ts
+++ b/src/rendering/ScoreBarRenderer.ts
@@ -63,7 +63,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         return (BarRendererBase.LineSpacing + 1) * this.scale;
     }
 
-    protected updateSizes(): void {
+    protected override updateSizes(): void {
         let res: RenderingResources = this.resources;
         let glyphOverflow: number = res.tablatureFont.size / 2 + res.tablatureFont.size * 0.2;
         this.topPadding = glyphOverflow * this.scale;
@@ -81,7 +81,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         this._firstLineY = (fullLineHeight - actualLineHeight) / 2;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.updateFirstLineY();
         super.doLayout();
         if (!this.bar.isEmpty && this.accidentalHelper.maxLineBeat) {
@@ -116,7 +116,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx, cy, canvas);
         this.paintBeams(cx, cy, canvas);
         this.paintTuplets(cx, cy, canvas);
@@ -377,11 +377,11 @@ export class ScoreBarRenderer extends BarRendererBase {
         return this.getScoreHeight(size);
     }
 
-    public get middleYPosition(): number {
+    public override get middleYPosition(): number {
         return this.getScoreY(this.bar.staff.standardNotationLineCount - 1);
     }
 
-    public getNoteY(note: Note, requestedPosition: NoteYPosition): number {
+    public override getNoteY(note: Note, requestedPosition: NoteYPosition): number {
         let y = super.getNoteY(note, requestedPosition);
         if (isNaN(y)) {
             // NOTE: some might request the note position before the glyphs have been created
@@ -397,7 +397,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         return this.calculateBeamYWithDirection(h, x, h.direction);
     }
 
-    public applyLayoutingInfo(): boolean {
+    public override applyLayoutingInfo(): boolean {
         const result = super.applyLayoutingInfo();
         if (result && this.bar.isMultiVoice) {
             // consider rest overflows
@@ -736,7 +736,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         }
     }
 
-    protected createPreBeatGlyphs(): void {
+    protected override createPreBeatGlyphs(): void {
         super.createPreBeatGlyphs();
         if (this.bar.masterBar.isRepeatStart) {
             this.addPreBeatGlyph(new RepeatOpenGlyph(0, 0, 1.5, 3));
@@ -793,7 +793,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         this.addPreBeatGlyph(new BarNumberGlyph(0, this.getScoreHeight(-0.5), this.bar.index + 1));
     }
 
-    protected createBeatGlyphs(): void {
+    protected override createBeatGlyphs(): void {
         for (let v: number = 0; v < this.bar.voices.length; v++) {
             let voice: Voice = this.bar.voices[v];
             if (this.hasVoiceContainer(voice)) {
@@ -802,7 +802,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         }
     }
 
-    protected createPostBeatGlyphs(): void {
+    protected override createPostBeatGlyphs(): void {
         super.createPostBeatGlyphs();
         if (this.bar.masterBar.isRepeatEnd) {
             this.addPostBeatGlyph(new RepeatCloseGlyph(this.x, 0));
@@ -902,7 +902,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         );
     }
 
-    protected createVoiceGlyphs(v: Voice): void {
+    protected override createVoiceGlyphs(v: Voice): void {
         for (let i: number = 0, j: number = v.beats.length; i < j; i++) {
             let b: Beat = v.beats[i];
             let container: ScoreBeatContainerGlyph = new ScoreBeatContainerGlyph(b, this.getVoiceContainer(v)!);
@@ -940,7 +940,7 @@ export class ScoreBarRenderer extends BarRendererBase {
     }
 
     // private static readonly Random Random = new Random();
-    protected paintBackground(cx: number, cy: number, canvas: ICanvas): void {
+    protected override paintBackground(cx: number, cy: number, canvas: ICanvas): void {
         super.paintBackground(cx, cy, canvas);
         let res: RenderingResources = this.resources;
         // canvas.color = Color.random(100);
@@ -959,7 +959,7 @@ export class ScoreBarRenderer extends BarRendererBase {
         this.paintSimileMark(cx, cy, canvas);
     }
 
-    public completeBeamingHelper(helper: BeamingHelper) {
+    public override completeBeamingHelper(helper: BeamingHelper) {
         // for multi-voice bars we need to register the positions 
         // for multi-voice rest displacement to avoid collisions
         if (this.bar.isMultiVoice && helper.highestNoteInHelper && helper.lowestNoteInHelper) {
diff --git a/src/rendering/ScoreBeatContainerGlyph.ts b/src/rendering/ScoreBeatContainerGlyph.ts
index 0b526dfa8..eb7d3f3f1 100644
--- a/src/rendering/ScoreBeatContainerGlyph.ts
+++ b/src/rendering/ScoreBeatContainerGlyph.ts
@@ -21,7 +21,7 @@ export class ScoreBeatContainerGlyph extends BeatContainerGlyph {
         super(beat, voiceContainer);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this._effectSlur = null;
         this._effectEndSlur = null;
         super.doLayout();
@@ -52,7 +52,7 @@ export class ScoreBeatContainerGlyph extends BeatContainerGlyph {
         }
     }
 
-    protected createTies(n: Note): void {
+    protected override createTies(n: Note): void {
         // create a tie if any effect requires it
         if (!n.isVisible) {
             return;
diff --git a/src/rendering/ScoreRenderer.ts b/src/rendering/ScoreRenderer.ts
index d63a89c45..4662fd92a 100644
--- a/src/rendering/ScoreRenderer.ts
+++ b/src/rendering/ScoreRenderer.ts
@@ -64,23 +64,27 @@ export class ScoreRenderer implements IScoreRenderer {
         return false;
     }
 
-    public renderScore(score: Score, trackIndexes: number[]): void {
+    public renderScore(score: Score | null, trackIndexes: number[] | null): void {
         try {
             this.score = score;
-            let tracks: Track[];
-            if (!trackIndexes) {
-                tracks = score.tracks.slice(0);
-            } else {
-                tracks = [];
-                for (let track of trackIndexes) {
-                    if (track >= 0 && track < score.tracks.length) {
-                        tracks.push(score.tracks[track]);
+            let tracks: Track[] | null = null;
+
+            if (score != null && trackIndexes != null) {
+                if (!trackIndexes) {
+                    tracks = score.tracks.slice(0);
+                } else {
+                    tracks = [];
+                    for (let track of trackIndexes) {
+                        if (track >= 0 && track < score.tracks.length) {
+                            tracks.push(score.tracks[track]);
+                        }
                     }
                 }
+                if (tracks.length === 0 && score.tracks.length > 0) {
+                    tracks.push(score.tracks[0]);
+                }
             }
-            if (tracks.length === 0 && score.tracks.length > 0) {
-                tracks.push(score.tracks[0]);
-            }
+
             this.tracks = tracks;
             this.render();
         } catch (e) {
@@ -126,21 +130,28 @@ export class ScoreRenderer implements IScoreRenderer {
             return;
         }
         this.boundsLookup = new BoundsLookup();
-        if (!this.tracks || this.tracks.length === 0) {
-            return;
-        }
         this.recreateCanvas();
         this.canvas!.lineWidth = this.settings.display.scale;
         this.canvas!.settings = this.settings;
-        Logger.debug('Rendering', 'Rendering ' + this.tracks.length + ' tracks');
-        for (let i: number = 0; i < this.tracks.length; i++) {
-            let track: Track = this.tracks[i];
-            Logger.debug('Rendering', 'Track ' + i + ': ' + track.name);
+
+        if (!this.tracks || this.tracks.length === 0 || !this.score) {
+            Logger.debug('Rendering', 'Clearing rendered tracks because no score or tracks are set');
+            (this.preRender as EventEmitterOfT<boolean>).trigger(false);
+            this._renderedTracks = null;
+            this.onRenderFinished();
+            (this.postRenderFinished as EventEmitter).trigger();
+            Logger.debug('Rendering', 'Clearing finished');
+        } else {
+            Logger.debug('Rendering', 'Rendering ' + this.tracks.length + ' tracks');
+            for (let i: number = 0; i < this.tracks.length; i++) {
+                let track: Track = this.tracks[i];
+                Logger.debug('Rendering', 'Track ' + i + ': ' + track.name);
+            }
+            (this.preRender as EventEmitterOfT<boolean>).trigger(false);
+            this.recreateLayout();
+            this.layoutAndRender();
+            Logger.debug('Rendering', 'Rendering finished');
         }
-        (this.preRender as EventEmitterOfT<boolean>).trigger(false);
-        this.recreateLayout();
-        this.layoutAndRender();
-        Logger.debug('Rendering', 'Rendering finished');
     }
 
     public resizeRender(): void {
diff --git a/src/rendering/TabBarRenderer.ts b/src/rendering/TabBarRenderer.ts
index 39dcc53df..434a5403e 100644
--- a/src/rendering/TabBarRenderer.ts
+++ b/src/rendering/TabBarRenderer.ts
@@ -51,7 +51,7 @@ export class TabBarRenderer extends BarRendererBase {
         return (TabBarRenderer.TabLineSpacing + 1) * this.scale;
     }
 
-    protected updateSizes(): void {
+    protected override updateSizes(): void {
         let res: RenderingResources = this.resources;
         let numberOverflow: number = (res.tablatureFont.size / 2 + res.tablatureFont.size * 0.2) * this.scale;
         this.topPadding = numberOverflow;
@@ -72,7 +72,7 @@ export class TabBarRenderer extends BarRendererBase {
         this._firstLineY = (res.tablatureFont.size / 2 + res.tablatureFont.size * 0.2) * this.scale;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.updateFirstLineY();
         super.doLayout();
         if (this.settings.notation.rhythmMode !== TabRhythmMode.Hidden) {
@@ -93,7 +93,7 @@ export class TabBarRenderer extends BarRendererBase {
         }
     }
 
-    protected createPreBeatGlyphs(): void {
+    protected override createPreBeatGlyphs(): void {
         super.createPreBeatGlyphs();
         if (this.bar.masterBar.isRepeatStart) {
             this.addPreBeatGlyph(new RepeatOpenGlyph(0, 0, 1.5, 3));
@@ -145,7 +145,7 @@ export class TabBarRenderer extends BarRendererBase {
         );
     }
 
-    protected createVoiceGlyphs(v: Voice): void {
+    protected override createVoiceGlyphs(v: Voice): void {
         for (let i: number = 0, j: number = v.beats.length; i < j; i++) {
             let b: Beat = v.beats[i];
             let container: TabBeatContainerGlyph = new TabBeatContainerGlyph(b, this.getVoiceContainer(v)!);
@@ -155,7 +155,7 @@ export class TabBarRenderer extends BarRendererBase {
         }
     }
 
-    protected createPostBeatGlyphs(): void {
+    protected override createPostBeatGlyphs(): void {
         super.createPostBeatGlyphs();
         if (this.bar.masterBar.isRepeatEnd) {
             this.addPostBeatGlyph(new RepeatCloseGlyph(this.x, 0));
@@ -181,11 +181,11 @@ export class TabBarRenderer extends BarRendererBase {
         return this.lineOffset * line;
     }
 
-    public get middleYPosition(): number {
+    public override get middleYPosition(): number {
         return this.getTabY(this.bar.staff.tuning.length - 1);
     }
 
-    protected paintBackground(cx: number, cy: number, canvas: ICanvas): void {
+    protected override paintBackground(cx: number, cy: number, canvas: ICanvas): void {
         super.paintBackground(cx, cy, canvas);
         let res: RenderingResources = this.resources;
         //
@@ -241,7 +241,7 @@ export class TabBarRenderer extends BarRendererBase {
         this.paintSimileMark(cx, cy, canvas);
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx, cy, canvas);
         if (this.settings.notation.rhythmMode !== TabRhythmMode.Hidden) {
             this.paintBeams(cx, cy, canvas);
diff --git a/src/rendering/TabBarRendererFactory.ts b/src/rendering/TabBarRendererFactory.ts
index f29b2a4bd..73d329b7b 100644
--- a/src/rendering/TabBarRendererFactory.ts
+++ b/src/rendering/TabBarRendererFactory.ts
@@ -26,7 +26,7 @@ export class TabBarRendererFactory extends BarRendererFactory {
         this.hideOnPercussionTrack = true;
     }
 
-    public canCreate(track: Track, staff: Staff): boolean {
+    public override canCreate(track: Track, staff: Staff): boolean {
         return staff.tuning.length > 0 && super.canCreate(track, staff);
     }
 
diff --git a/src/rendering/effects/DummyEffectGlyph.ts b/src/rendering/effects/DummyEffectGlyph.ts
index 579805218..12a0c8082 100644
--- a/src/rendering/effects/DummyEffectGlyph.ts
+++ b/src/rendering/effects/DummyEffectGlyph.ts
@@ -12,12 +12,12 @@ export class DummyEffectGlyph extends EffectGlyph {
         this._h = h;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = this._w * this.scale;
         this.height = this._h * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let c = canvas.color; 
         canvas.color = Color.random();
         canvas.fillRect(cx + this.x, cy + this.y, this.width, this.height);
diff --git a/src/rendering/effects/HarmonicsEffectInfo.ts b/src/rendering/effects/HarmonicsEffectInfo.ts
index b85a03166..74b38b181 100644
--- a/src/rendering/effects/HarmonicsEffectInfo.ts
+++ b/src/rendering/effects/HarmonicsEffectInfo.ts
@@ -13,7 +13,7 @@ export class HarmonicsEffectInfo extends NoteEffectInfoBase {
     private _beat: Beat | null = null;
     private _effectId: string;
 
-    public get effectId(): string {
+    public override get effectId(): string {
         return this._effectId;
     }
 
diff --git a/src/rendering/effects/OttaviaEffectInfo.ts b/src/rendering/effects/OttaviaEffectInfo.ts
index 34da9c1b8..a95098696 100644
--- a/src/rendering/effects/OttaviaEffectInfo.ts
+++ b/src/rendering/effects/OttaviaEffectInfo.ts
@@ -11,7 +11,7 @@ import { NotationElement } from '@src/NotationSettings';
 export class OttaviaEffectInfo extends EffectBarRendererInfo {
     private _aboveStaff: boolean;
 
-    public get effectId(): string {
+    public override get effectId(): string {
         return 'ottavia-' + (this._aboveStaff ? 'above' : 'below');
     }
 
diff --git a/src/rendering/glyphs/AccentuationGlyph.ts b/src/rendering/glyphs/AccentuationGlyph.ts
index ac0d290aa..1e295057c 100644
--- a/src/rendering/glyphs/AccentuationGlyph.ts
+++ b/src/rendering/glyphs/AccentuationGlyph.ts
@@ -21,12 +21,12 @@ export class AccentuationGlyph extends MusicFontGlyph {
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 9 * this.scale;
         this.height = 9 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx - 2 * this.scale, cy + this.height, canvas);
     }
 }
diff --git a/src/rendering/glyphs/AccidentalGlyph.ts b/src/rendering/glyphs/AccidentalGlyph.ts
index 1712cdd41..2dc75ec0c 100644
--- a/src/rendering/glyphs/AccidentalGlyph.ts
+++ b/src/rendering/glyphs/AccidentalGlyph.ts
@@ -35,7 +35,7 @@ export class AccidentalGlyph extends MusicFontGlyph {
         return MusicFontSymbol.None;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         switch (this._accidentalType) {
             case AccidentalType.DoubleFlat:
                 this.width = 18;
diff --git a/src/rendering/glyphs/AccidentalGroupGlyph.ts b/src/rendering/glyphs/AccidentalGroupGlyph.ts
index 1f40ed60d..4bf76e7ef 100644
--- a/src/rendering/glyphs/AccidentalGroupGlyph.ts
+++ b/src/rendering/glyphs/AccidentalGroupGlyph.ts
@@ -13,7 +13,7 @@ export class AccidentalGroupGlyph extends GlyphGroup {
         super(0, 0);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         if (!this.glyphs || this.glyphs.length === 0) {
             this.width = 0;
             return;
diff --git a/src/rendering/glyphs/AlternateEndingsGlyph.ts b/src/rendering/glyphs/AlternateEndingsGlyph.ts
index 3f4740724..956d0d353 100644
--- a/src/rendering/glyphs/AlternateEndingsGlyph.ts
+++ b/src/rendering/glyphs/AlternateEndingsGlyph.ts
@@ -18,7 +18,7 @@ export class AlternateEndingsGlyph extends EffectGlyph {
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = this.renderer.resources.wordsFont.size + (AlternateEndingsGlyph.Padding * this.scale + 2);
         let endingsStrings: string = '';
@@ -29,7 +29,7 @@ export class AlternateEndingsGlyph extends EffectGlyph {
         this._endingsString = endingsStrings;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx, cy, canvas);
         let baseline: TextBaseline = canvas.textBaseline;
         canvas.textBaseline = TextBaseline.Top;
diff --git a/src/rendering/glyphs/ArticStaccatoAboveGlyph.ts b/src/rendering/glyphs/ArticStaccatoAboveGlyph.ts
index 0460ca19a..7470050c3 100644
--- a/src/rendering/glyphs/ArticStaccatoAboveGlyph.ts
+++ b/src/rendering/glyphs/ArticStaccatoAboveGlyph.ts
@@ -8,12 +8,12 @@ export class ArticStaccatoAboveGlyph extends MusicFontGlyph {
         super(x, y, NoteHeadGlyph.GraceScale, MusicFontSymbol.ArticStaccatoAbove);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = NoteHeadGlyph.QuarterNoteHeadWidth * this.scale;
         this.height = 7 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx + 3 * this.scale, cy + 5 * this.scale, canvas);
     }
 }
diff --git a/src/rendering/glyphs/BarNumberGlyph.ts b/src/rendering/glyphs/BarNumberGlyph.ts
index f285be205..8f1cb4eac 100644
--- a/src/rendering/glyphs/BarNumberGlyph.ts
+++ b/src/rendering/glyphs/BarNumberGlyph.ts
@@ -11,12 +11,12 @@ export class BarNumberGlyph extends Glyph {
         this._number = num;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.renderer.scoreRenderer.canvas!.font = this.renderer.resources.barNumberFont;
         this.width = this.renderer.scoreRenderer.canvas!.measureText(this._number.toString()) + 5 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         if (!this.renderer.staff.isFirstInAccolade) {
             return;
         }
diff --git a/src/rendering/glyphs/BarSeperatorGlyph.ts b/src/rendering/glyphs/BarSeperatorGlyph.ts
index ca1eb9698..20256656b 100644
--- a/src/rendering/glyphs/BarSeperatorGlyph.ts
+++ b/src/rendering/glyphs/BarSeperatorGlyph.ts
@@ -6,7 +6,7 @@ export class BarSeperatorGlyph extends Glyph {
         super(x, y);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         if (this.renderer.isLast) {
             this.width = 15 * this.scale;
         } else if (
@@ -23,7 +23,7 @@ export class BarSeperatorGlyph extends Glyph {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let blockWidth: number = 4 * this.scale;
         let top: number = cy + this.y + this.renderer.topPadding;
         let bottom: number = cy + this.y + this.renderer.height - this.renderer.bottomPadding;
diff --git a/src/rendering/glyphs/BeatContainerGlyph.ts b/src/rendering/glyphs/BeatContainerGlyph.ts
index 6f90c20dd..c1df82293 100644
--- a/src/rendering/glyphs/BeatContainerGlyph.ts
+++ b/src/rendering/glyphs/BeatContainerGlyph.ts
@@ -76,7 +76,7 @@ export class BeatContainerGlyph extends Glyph {
         this.updateWidth();
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.preNotes.x = 0;
         this.preNotes.renderer = this.renderer;
         this.preNotes.container = this;
@@ -136,7 +136,7 @@ export class BeatContainerGlyph extends Glyph {
         return 'b' + beat.id;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         if (this.beat.voice.isEmpty) {
             return;
         }
diff --git a/src/rendering/glyphs/BeatGlyphBase.ts b/src/rendering/glyphs/BeatGlyphBase.ts
index 958c953f2..1c9066036 100644
--- a/src/rendering/glyphs/BeatGlyphBase.ts
+++ b/src/rendering/glyphs/BeatGlyphBase.ts
@@ -11,7 +11,7 @@ export class BeatGlyphBase extends GlyphGroup {
         super(0, 0);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         // left to right layout
         let w: number = 0;
         if (this.glyphs) {
diff --git a/src/rendering/glyphs/BeatVibratoGlyph.ts b/src/rendering/glyphs/BeatVibratoGlyph.ts
index 2bf97313c..5b242e6e6 100644
--- a/src/rendering/glyphs/BeatVibratoGlyph.ts
+++ b/src/rendering/glyphs/BeatVibratoGlyph.ts
@@ -12,7 +12,7 @@ export class BeatVibratoGlyph extends GroupedEffectGlyph {
         this._type = type;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         switch (this._type) {
             case VibratoType.Slight:
diff --git a/src/rendering/glyphs/BendNoteHeadGroupGlyph.ts b/src/rendering/glyphs/BendNoteHeadGroupGlyph.ts
index df192d462..b318ed6a3 100644
--- a/src/rendering/glyphs/BendNoteHeadGroupGlyph.ts
+++ b/src/rendering/glyphs/BendNoteHeadGroupGlyph.ts
@@ -77,7 +77,7 @@ export class BendNoteHeadGroupGlyph extends ScoreNoteChordGlyphBase {
         this.isEmpty = false;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let x: number = 0;
         if (this._showParenthesis) {
             this._preNoteParenthesis!.x = x;
@@ -103,7 +103,7 @@ export class BendNoteHeadGroupGlyph extends ScoreNoteChordGlyphBase {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         // canvas.Color = Color.Random();
         // canvas.FillRect(cx + X, cy + Y, Width, 10);
         // canvas.Color = Renderer.Resources.MainGlyphColor;
diff --git a/src/rendering/glyphs/ChordDiagramGlyph.ts b/src/rendering/glyphs/ChordDiagramGlyph.ts
index 7e9e2f489..e7b26d8b4 100644
--- a/src/rendering/glyphs/ChordDiagramGlyph.ts
+++ b/src/rendering/glyphs/ChordDiagramGlyph.ts
@@ -21,7 +21,7 @@ export class ChordDiagramGlyph extends EffectGlyph {
         this._chord = chord;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         const scale = this.scale;
         let res: RenderingResources = this.renderer.resources;
@@ -43,7 +43,7 @@ export class ChordDiagramGlyph extends EffectGlyph {
             2 * ChordDiagramGlyph.Padding * scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         cx += this.x + ChordDiagramGlyph.Padding * this.scale + this._firstFretSpacing;
         cy += this.y;
         let w: number = this.width - 2 * ChordDiagramGlyph.Padding * this.scale + this.scale - this._firstFretSpacing;
diff --git a/src/rendering/glyphs/CircleGlyph.ts b/src/rendering/glyphs/CircleGlyph.ts
index 5b5392655..fd6d09e40 100644
--- a/src/rendering/glyphs/CircleGlyph.ts
+++ b/src/rendering/glyphs/CircleGlyph.ts
@@ -9,11 +9,11 @@ export class CircleGlyph extends Glyph {
         this._size = size;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = this._size + 3 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         canvas.fillCircle(cx + this.x, cy + this.y, this._size);
     }
 }
diff --git a/src/rendering/glyphs/ClefGlyph.ts b/src/rendering/glyphs/ClefGlyph.ts
index ad02ab76f..f75f0cac8 100644
--- a/src/rendering/glyphs/ClefGlyph.ts
+++ b/src/rendering/glyphs/ClefGlyph.ts
@@ -15,7 +15,7 @@ export class ClefGlyph extends MusicFontGlyph {
         this._clefOttava = clefOttava;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         switch (this._clef) {
             case Clef.Neutral:
                 this.width = 15 * this.scale;
@@ -46,7 +46,7 @@ export class ClefGlyph extends MusicFontGlyph {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx, cy, canvas);
         let numberGlyph: Glyph;
         let top: boolean = false;
diff --git a/src/rendering/glyphs/CrescendoGlyph.ts b/src/rendering/glyphs/CrescendoGlyph.ts
index a685c6064..51355202f 100644
--- a/src/rendering/glyphs/CrescendoGlyph.ts
+++ b/src/rendering/glyphs/CrescendoGlyph.ts
@@ -16,7 +16,7 @@ export class CrescendoGlyph extends GroupedEffectGlyph {
         this.y = y;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = 17 * this.scale;
     }
diff --git a/src/rendering/glyphs/DeadNoteHeadGlyph.ts b/src/rendering/glyphs/DeadNoteHeadGlyph.ts
index 80395aefd..0cb828da7 100644
--- a/src/rendering/glyphs/DeadNoteHeadGlyph.ts
+++ b/src/rendering/glyphs/DeadNoteHeadGlyph.ts
@@ -10,7 +10,7 @@ export class DeadNoteHeadGlyph extends MusicFontGlyph {
         this._isGrace = isGrace;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 9 * (this._isGrace ? NoteHeadGlyph.GraceScale : 1) * this.scale;
         this.height = NoteHeadGlyph.NoteHeadHeight * this.scale;
     }
diff --git a/src/rendering/glyphs/DiamondNoteHeadGlyph.ts b/src/rendering/glyphs/DiamondNoteHeadGlyph.ts
index 58e7135f2..5821005c5 100644
--- a/src/rendering/glyphs/DiamondNoteHeadGlyph.ts
+++ b/src/rendering/glyphs/DiamondNoteHeadGlyph.ts
@@ -23,7 +23,7 @@ export class DiamondNoteHeadGlyph extends MusicFontGlyph {
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 9 * (this._isGrace ? NoteHeadGlyph.GraceScale : 1) * this.scale;
         this.height = NoteHeadGlyph.NoteHeadHeight * this.scale;
     }
diff --git a/src/rendering/glyphs/DigitGlyph.ts b/src/rendering/glyphs/DigitGlyph.ts
index ab48188d0..41bd0f988 100644
--- a/src/rendering/glyphs/DigitGlyph.ts
+++ b/src/rendering/glyphs/DigitGlyph.ts
@@ -11,7 +11,7 @@ export class DigitGlyph extends MusicFontGlyph {
         this._scale = scale;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = this.getDigitWidth(this._digit) * this.scale * this._scale;
     }
 
diff --git a/src/rendering/glyphs/DynamicsGlyph.ts b/src/rendering/glyphs/DynamicsGlyph.ts
index 0184e589d..8a4326a8d 100644
--- a/src/rendering/glyphs/DynamicsGlyph.ts
+++ b/src/rendering/glyphs/DynamicsGlyph.ts
@@ -7,7 +7,7 @@ export class DynamicsGlyph extends MusicFontGlyph {
         super(x, y, 0.6, DynamicsGlyph.getSymbol(dynamics));
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = 17 * this.scale;
         this.y += this.height / 2;
diff --git a/src/rendering/glyphs/FadeInGlyph.ts b/src/rendering/glyphs/FadeInGlyph.ts
index d9e9254d9..c94e873a7 100644
--- a/src/rendering/glyphs/FadeInGlyph.ts
+++ b/src/rendering/glyphs/FadeInGlyph.ts
@@ -2,12 +2,12 @@ import { ICanvas } from '@src/platform/ICanvas';
 import { EffectGlyph } from '@src/rendering/glyphs/EffectGlyph';
 
 export class FadeInGlyph extends EffectGlyph {
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = 17 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let size: number = 6 * this.scale;
         let width: number = Math.max(this.width, 14 * this.scale);
         let offset: number = this.height / 2;
diff --git a/src/rendering/glyphs/FermataGlyph.ts b/src/rendering/glyphs/FermataGlyph.ts
index 361b30591..111fb5621 100644
--- a/src/rendering/glyphs/FermataGlyph.ts
+++ b/src/rendering/glyphs/FermataGlyph.ts
@@ -21,12 +21,12 @@ export class FermataGlyph extends MusicFontGlyph {
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 23 * this.scale;
         this.height = 12 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx - this.width / 2, cy + this.height, canvas);
     }
 }
diff --git a/src/rendering/glyphs/FlagGlyph.ts b/src/rendering/glyphs/FlagGlyph.ts
index 4b6a2b213..b4e2d8550 100644
--- a/src/rendering/glyphs/FlagGlyph.ts
+++ b/src/rendering/glyphs/FlagGlyph.ts
@@ -11,7 +11,7 @@ export class FlagGlyph extends MusicFontGlyph {
         super(x, y, isGrace ? NoteHeadGlyph.GraceScale : 1, FlagGlyph.getSymbol(duration, direction, isGrace));
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 0;
     }
 
diff --git a/src/rendering/glyphs/GhostNoteContainerGlyph.ts b/src/rendering/glyphs/GhostNoteContainerGlyph.ts
index 840577a82..ccc81cece 100644
--- a/src/rendering/glyphs/GhostNoteContainerGlyph.ts
+++ b/src/rendering/glyphs/GhostNoteContainerGlyph.ts
@@ -52,7 +52,7 @@ export class GhostNoteContainerGlyph extends Glyph {
         return false;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let sr: ScoreBarRenderer = this.renderer as ScoreBarRenderer;
         this._infos.sort((a, b) => {
             return a.line - b.line;
@@ -79,7 +79,7 @@ export class GhostNoteContainerGlyph extends Glyph {
         this.width = this._glyphs.length > 0 ? this._glyphs[0].width : 0;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx, cy, canvas);
         for (let g of this._glyphs) {
             g.paint(cx + this.x, cy + this.y, canvas);
diff --git a/src/rendering/glyphs/GhostParenthesisGlyph.ts b/src/rendering/glyphs/GhostParenthesisGlyph.ts
index 9813adf98..6fae22fcc 100644
--- a/src/rendering/glyphs/GhostParenthesisGlyph.ts
+++ b/src/rendering/glyphs/GhostParenthesisGlyph.ts
@@ -11,12 +11,12 @@ export class GhostParenthesisGlyph extends Glyph {
         this._isOpen = isOpen;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.width = GhostParenthesisGlyph.Size * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         if (this._isOpen) {
             TieGlyph.paintTie(
                 canvas,
diff --git a/src/rendering/glyphs/GlyphGroup.ts b/src/rendering/glyphs/GlyphGroup.ts
index d2d23b68f..dfb177230 100644
--- a/src/rendering/glyphs/GlyphGroup.ts
+++ b/src/rendering/glyphs/GlyphGroup.ts
@@ -16,7 +16,7 @@ export class GlyphGroup extends Glyph {
         super(x, y);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         if (!this.glyphs || this.glyphs.length === 0) {
             this.width = 0;
             return;
@@ -41,7 +41,7 @@ export class GlyphGroup extends Glyph {
         this.glyphs.push(g);
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let glyphs = this.glyphs;
         if (!glyphs || glyphs.length === 0) {
             return;
diff --git a/src/rendering/glyphs/GroupedEffectGlyph.ts b/src/rendering/glyphs/GroupedEffectGlyph.ts
index 85c70c703..4d8ece9e8 100644
--- a/src/rendering/glyphs/GroupedEffectGlyph.ts
+++ b/src/rendering/glyphs/GroupedEffectGlyph.ts
@@ -26,7 +26,7 @@ export abstract class GroupedEffectGlyph extends EffectGlyph {
         );
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         // if we are linked with the previous, the first glyph of the group will also render this one.
         if (this.isLinkedWithPrevious) {
             return;
diff --git a/src/rendering/glyphs/GuitarGolpeGlyph.ts b/src/rendering/glyphs/GuitarGolpeGlyph.ts
index 046bc3f80..0e4e2deae 100644
--- a/src/rendering/glyphs/GuitarGolpeGlyph.ts
+++ b/src/rendering/glyphs/GuitarGolpeGlyph.ts
@@ -8,12 +8,12 @@ export class GuitarGolpeGlyph extends MusicFontGlyph {
         super(x, y, NoteHeadGlyph.GraceScale, MusicFontSymbol.GuitarGolpe);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 9 * this.scale;
         this.height = 10 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx, cy + this.height, canvas);
     }
 }
diff --git a/src/rendering/glyphs/LeftHandTapGlyph.ts b/src/rendering/glyphs/LeftHandTapGlyph.ts
index e29650ed5..830e51235 100644
--- a/src/rendering/glyphs/LeftHandTapGlyph.ts
+++ b/src/rendering/glyphs/LeftHandTapGlyph.ts
@@ -9,13 +9,13 @@ export class LeftHandTapGlyph extends EffectGlyph {
         super(0, 0);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         const font = this.renderer.resources.effectFont;
         this.height = font.size + LeftHandTapGlyph.Padding * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let res: RenderingResources = this.renderer.resources;
         canvas.font = res.effectFont;
         let old: TextAlign = canvas.textAlign;
diff --git a/src/rendering/glyphs/LeftToRightLayoutingGlyphGroup.ts b/src/rendering/glyphs/LeftToRightLayoutingGlyphGroup.ts
index 62e4b200b..7565a1c65 100644
--- a/src/rendering/glyphs/LeftToRightLayoutingGlyphGroup.ts
+++ b/src/rendering/glyphs/LeftToRightLayoutingGlyphGroup.ts
@@ -7,7 +7,7 @@ export class LeftToRightLayoutingGlyphGroup extends GlyphGroup {
         this.glyphs = [];
     }
 
-    public addGlyph(g: Glyph): void {
+    public override addGlyph(g: Glyph): void {
         g.x =
             this.glyphs!.length === 0
                 ? 0
diff --git a/src/rendering/glyphs/LineRangedGlyph.ts b/src/rendering/glyphs/LineRangedGlyph.ts
index 572c84e43..c427dfb3a 100644
--- a/src/rendering/glyphs/LineRangedGlyph.ts
+++ b/src/rendering/glyphs/LineRangedGlyph.ts
@@ -15,7 +15,7 @@ export class LineRangedGlyph extends GroupedEffectGlyph {
         this._label = label;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         if (this.renderer.settings.notation.extendLineEffectsToBeatEnd) {
             this.endPosition = BeatXPosition.EndBeat;
             this.forceGroupedRendering = true;
@@ -24,7 +24,7 @@ export class LineRangedGlyph extends GroupedEffectGlyph {
         this.height = this.renderer.resources.effectFont.size;
     }
 
-    protected paintNonGrouped(cx: number, cy: number, canvas: ICanvas): void {
+    protected override paintNonGrouped(cx: number, cy: number, canvas: ICanvas): void {
         let res: RenderingResources = this.renderer.resources;
         canvas.font = res.effectFont;
         let x: TextAlign = canvas.textAlign;
diff --git a/src/rendering/glyphs/LyricsGlyph.ts b/src/rendering/glyphs/LyricsGlyph.ts
index 0a03654ea..90cf0785c 100644
--- a/src/rendering/glyphs/LyricsGlyph.ts
+++ b/src/rendering/glyphs/LyricsGlyph.ts
@@ -15,12 +15,12 @@ export class LyricsGlyph extends EffectGlyph {
         this.textAlign = textAlign;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = this.font.size * this._lines.length;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         canvas.font = this.font;
         let old: TextAlign = canvas.textAlign;
         canvas.textAlign = this.textAlign;
diff --git a/src/rendering/glyphs/MusicFontGlyph.ts b/src/rendering/glyphs/MusicFontGlyph.ts
index 960fd37c2..858528110 100644
--- a/src/rendering/glyphs/MusicFontGlyph.ts
+++ b/src/rendering/glyphs/MusicFontGlyph.ts
@@ -12,7 +12,7 @@ export class MusicFontGlyph extends EffectGlyph {
         this.symbol = symbol;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         canvas.fillMusicFontSymbol(cx + this.x, cy + this.y, this.glyphScale * this.scale, this.symbol, false);
     }
 }
diff --git a/src/rendering/glyphs/NoteHeadGlyph.ts b/src/rendering/glyphs/NoteHeadGlyph.ts
index cf1b326f4..7f79f43ac 100644
--- a/src/rendering/glyphs/NoteHeadGlyph.ts
+++ b/src/rendering/glyphs/NoteHeadGlyph.ts
@@ -16,12 +16,12 @@ export class NoteHeadGlyph extends MusicFontGlyph {
         this._duration = duration;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let offset: number = this._isGrace ? this.scale : 0;
         canvas.fillMusicFontSymbol(cx + this.x, cy + this.y + offset, this.glyphScale * this.scale, this.symbol, false);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let scale: number = (this._isGrace ? NoteHeadGlyph.GraceScale : 1) * this.scale;
         switch (this._duration) {
             case Duration.QuadrupleWhole:
diff --git a/src/rendering/glyphs/NoteNumberGlyph.ts b/src/rendering/glyphs/NoteNumberGlyph.ts
index 0f09ac9c9..80f98c040 100644
--- a/src/rendering/glyphs/NoteNumberGlyph.ts
+++ b/src/rendering/glyphs/NoteNumberGlyph.ts
@@ -24,7 +24,7 @@ export class NoteNumberGlyph extends Glyph {
         this._note = note;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let n: Note = this._note;
         let fret: number = n.fret - n.beat.voice.bar.staff.transpositionPitch;
         if (n.harmonicType === HarmonicType.Natural && n.harmonicValue !== 0) {
@@ -91,7 +91,7 @@ export class NoteNumberGlyph extends Glyph {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         if (this.isEmpty) {
             return;
         }
diff --git a/src/rendering/glyphs/NoteVibratoGlyph.ts b/src/rendering/glyphs/NoteVibratoGlyph.ts
index ce2e3075c..c5be86739 100644
--- a/src/rendering/glyphs/NoteVibratoGlyph.ts
+++ b/src/rendering/glyphs/NoteVibratoGlyph.ts
@@ -20,7 +20,7 @@ export class NoteVibratoGlyph extends GroupedEffectGlyph {
         this._partialWaves = partialWaves;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         let symbolHeight: number = 0;
         switch (this._type) {
diff --git a/src/rendering/glyphs/NumberGlyph.ts b/src/rendering/glyphs/NumberGlyph.ts
index 2a859964d..10d33f7d6 100644
--- a/src/rendering/glyphs/NumberGlyph.ts
+++ b/src/rendering/glyphs/NumberGlyph.ts
@@ -14,7 +14,7 @@ export class NumberGlyph extends GlyphGroup {
         this._scale = scale;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let i: number = this._number;
         while (i > 0) {
             let num: number = i % 10;
diff --git a/src/rendering/glyphs/OttavaGlyph.ts b/src/rendering/glyphs/OttavaGlyph.ts
index 512707f3a..96b99559d 100644
--- a/src/rendering/glyphs/OttavaGlyph.ts
+++ b/src/rendering/glyphs/OttavaGlyph.ts
@@ -14,12 +14,12 @@ export class OttavaGlyph extends GroupedEffectGlyph {
         this._aboveStaff = aboveStaff;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = 13 * this.scale;
     }
 
-    protected paintNonGrouped(cx: number, cy: number, canvas: ICanvas): void {
+    protected override paintNonGrouped(cx: number, cy: number, canvas: ICanvas): void {
         this.paintOttava(cx, cy, canvas);
     }
 
diff --git a/src/rendering/glyphs/PercussionNoteHeadGlyph.ts b/src/rendering/glyphs/PercussionNoteHeadGlyph.ts
index b0dd35f67..4e0919398 100644
--- a/src/rendering/glyphs/PercussionNoteHeadGlyph.ts
+++ b/src/rendering/glyphs/PercussionNoteHeadGlyph.ts
@@ -15,7 +15,7 @@ export class PercussionNoteHeadGlyph extends MusicFontGlyph {
         this._articulation = articulation;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let offset: number = this._isGrace ? this.scale : 0;
         canvas.fillMusicFontSymbol(cx + this.x, cy + this.y + offset, this.glyphScale * this.scale, this.symbol, false);
 
@@ -24,7 +24,7 @@ export class PercussionNoteHeadGlyph extends MusicFontGlyph {
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let scale: number = (this._isGrace ? NoteHeadGlyph.GraceScale : 1) * this.scale;
         switch (this.symbol) {
             case MusicFontSymbol.NoteheadWhole:
diff --git a/src/rendering/glyphs/PickStrokeGlyph.ts b/src/rendering/glyphs/PickStrokeGlyph.ts
index 99aa950be..0673b77d8 100644
--- a/src/rendering/glyphs/PickStrokeGlyph.ts
+++ b/src/rendering/glyphs/PickStrokeGlyph.ts
@@ -10,12 +10,12 @@ export class PickStrokeGlyph extends MusicFontGlyph {
         super(x, y, NoteHeadGlyph.GraceScale, PickStrokeGlyph.getSymbol(pickStroke));
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 9 * this.scale;
         this.height = 13 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx, cy + this.height, canvas);
     }
 
diff --git a/src/rendering/glyphs/PictEdgeOfCymbalGlyph.ts b/src/rendering/glyphs/PictEdgeOfCymbalGlyph.ts
index 4610c8bb5..2bf7679c6 100644
--- a/src/rendering/glyphs/PictEdgeOfCymbalGlyph.ts
+++ b/src/rendering/glyphs/PictEdgeOfCymbalGlyph.ts
@@ -7,12 +7,12 @@ export class PictEdgeOfCymbalGlyph extends MusicFontGlyph {
         super(x, y, 0.5, MusicFontSymbol.PictEdgeOfCymbal);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 22 * this.scale;
         this.height = 15 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         super.paint(cx - 3 * this.scale, cy + this.height, canvas);
     }
 }
diff --git a/src/rendering/glyphs/RepeatCloseGlyph.ts b/src/rendering/glyphs/RepeatCloseGlyph.ts
index 93fb4479e..94f8c6735 100644
--- a/src/rendering/glyphs/RepeatCloseGlyph.ts
+++ b/src/rendering/glyphs/RepeatCloseGlyph.ts
@@ -6,11 +6,11 @@ export class RepeatCloseGlyph extends Glyph {
         super(x, y);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 11 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let blockWidth: number = 4 * this.scale;
         let top: number = cy + this.y + this.renderer.topPadding;
         let bottom: number = cy + this.y + this.renderer.height - this.renderer.bottomPadding;
diff --git a/src/rendering/glyphs/RepeatCountGlyph.ts b/src/rendering/glyphs/RepeatCountGlyph.ts
index d3858cfbb..32708841d 100644
--- a/src/rendering/glyphs/RepeatCountGlyph.ts
+++ b/src/rendering/glyphs/RepeatCountGlyph.ts
@@ -11,11 +11,11 @@ export class RepeatCountGlyph extends Glyph {
         this._count = count;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 0;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let res: RenderingResources = this.renderer.resources;
         let oldAlign: TextAlign = canvas.textAlign;
         canvas.font = res.barNumberFont;
diff --git a/src/rendering/glyphs/RepeatOpenGlyph.ts b/src/rendering/glyphs/RepeatOpenGlyph.ts
index 819e69adc..79970ca7e 100644
--- a/src/rendering/glyphs/RepeatOpenGlyph.ts
+++ b/src/rendering/glyphs/RepeatOpenGlyph.ts
@@ -13,11 +13,11 @@ export class RepeatOpenGlyph extends Glyph {
         this._circleSize = circleSize;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 13 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let blockWidth: number = 4 * this.scale;
         let top: number = cy + this.y + this.renderer.topPadding;
         let bottom: number = cy + this.y + this.renderer.height - this.renderer.bottomPadding;
diff --git a/src/rendering/glyphs/RowContainerGlyph.ts b/src/rendering/glyphs/RowContainerGlyph.ts
index 49da572bd..9934315d1 100644
--- a/src/rendering/glyphs/RowContainerGlyph.ts
+++ b/src/rendering/glyphs/RowContainerGlyph.ts
@@ -14,7 +14,7 @@ export class RowContainerGlyph extends GlyphGroup {
         this._align = align;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let x: number = 0;
         let y: number = 0;
         let padding: number = 2 * RowContainerGlyph.Padding * this.scale;
@@ -46,7 +46,7 @@ export class RowContainerGlyph extends GlyphGroup {
         this.height = y + padding;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         for (let row of this._rows) {
             row.paint(cx + this.x, cy + this.y + RowContainerGlyph.Padding * this.scale, canvas);
         }
diff --git a/src/rendering/glyphs/RowGlyphContainer.ts b/src/rendering/glyphs/RowGlyphContainer.ts
index 5ff2586f9..3b392dcc4 100644
--- a/src/rendering/glyphs/RowGlyphContainer.ts
+++ b/src/rendering/glyphs/RowGlyphContainer.ts
@@ -12,7 +12,7 @@ export class RowGlyphContainer extends GlyphGroup {
         this._align = align;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let x: number = 0;
         switch (this._align) {
             case TextAlign.Left:
diff --git a/src/rendering/glyphs/ScoreBeatGlyph.ts b/src/rendering/glyphs/ScoreBeatGlyph.ts
index be2151ce5..35d1c1602 100644
--- a/src/rendering/glyphs/ScoreBeatGlyph.ts
+++ b/src/rendering/glyphs/ScoreBeatGlyph.ts
@@ -38,21 +38,21 @@ export class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
     public noteHeads: ScoreNoteChordGlyph | null = null;
     public restGlyph: ScoreRestGlyph | null = null;
 
-    public getNoteX(note: Note, requestedPosition: NoteXPosition): number {
+    public override getNoteX(note: Note, requestedPosition: NoteXPosition): number {
         return this.noteHeads ? this.noteHeads.getNoteX(note, requestedPosition) : 0;
     }
 
-    public buildBoundingsLookup(beatBounds: BeatBounds, cx: number, cy: number) {
+    public override buildBoundingsLookup(beatBounds: BeatBounds, cx: number, cy: number) {
         if (this.noteHeads) {
             this.noteHeads.buildBoundingsLookup(beatBounds, cx + this.x, cy + this.y);
         }
     }
 
-    public getNoteY(note: Note, requestedPosition: NoteYPosition): number {
+    public override getNoteY(note: Note, requestedPosition: NoteYPosition): number {
         return this.noteHeads ? this.noteHeads.getNoteY(note, requestedPosition) : 0;
     }
 
-    public updateBeamingHelper(): void {
+    public override updateBeamingHelper(): void {
         if (this.noteHeads) {
             this.noteHeads.updateBeamingHelper(this.container.x + this.x);
         } else if (this.restGlyph) {
@@ -72,13 +72,13 @@ export class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         if (!this._skipPaint) {
             super.paint(cx, cy, canvas);
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         // create glyphs
         let sr: ScoreBarRenderer = this.renderer as ScoreBarRenderer;
         if (!this.container.beat.isEmpty) {
diff --git a/src/rendering/glyphs/ScoreBeatPreNotesGlyph.ts b/src/rendering/glyphs/ScoreBeatPreNotesGlyph.ts
index 0f5c3a67f..f9ecb8be0 100644
--- a/src/rendering/glyphs/ScoreBeatPreNotesGlyph.ts
+++ b/src/rendering/glyphs/ScoreBeatPreNotesGlyph.ts
@@ -23,7 +23,7 @@ export class ScoreBeatPreNotesGlyph extends BeatGlyphBase {
 
     public accidentals: AccidentalGroupGlyph | null = null;
 
-    public doLayout(): void {
+    public override doLayout(): void {
         if (!this.container.beat.isRest) {
             let accidentals: AccidentalGroupGlyph = new AccidentalGroupGlyph();
             accidentals.renderer = this.renderer;
@@ -42,7 +42,7 @@ export class ScoreBeatPreNotesGlyph extends BeatGlyphBase {
                             case BendType.Prebend:
                             case BendType.PrebendRelease:
                                 preBends.addGlyph(
-                                    note.displayValue - ((note.bendPoints[0].value / 2) | 0),
+                                    note.displayValue - ((note.bendPoints![0].value / 2) | 0),
                                     false
                                 );
                                 break;
@@ -52,7 +52,7 @@ export class ScoreBeatPreNotesGlyph extends BeatGlyphBase {
                             case WhammyType.PrediveDive:
                             case WhammyType.Predive:
                                 this._prebends.addGlyph(
-                                    note.displayValue - ((note.beat.whammyBarPoints[0].value / 2) | 0),
+                                    note.displayValue - ((note.beat.whammyBarPoints![0].value / 2) | 0),
                                     false
                                 );
                                 break;
diff --git a/src/rendering/glyphs/ScoreBendGlyph.ts b/src/rendering/glyphs/ScoreBendGlyph.ts
index f30cdbbb4..3b746c0b3 100644
--- a/src/rendering/glyphs/ScoreBendGlyph.ts
+++ b/src/rendering/glyphs/ScoreBendGlyph.ts
@@ -43,7 +43,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                         this._endNoteGlyph = endGlyphs;
                         this.BendNoteHeads.push(endGlyphs);
                     }
-                    let lastBendPoint: BendPoint = note.bendPoints[note.bendPoints.length - 1];
+                    let lastBendPoint: BendPoint = note.bendPoints![note.bendPoints!.length - 1];
                     endGlyphs.addGlyph(this.getBendNoteValue(note, lastBendPoint), lastBendPoint.value % 2 !== 0);
                 }
                 break;
@@ -57,7 +57,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                             this._endNoteGlyph  = endGlyphs;
                             this.BendNoteHeads.push(endGlyphs);
                         }
-                        let lastBendPoint: BendPoint = note.bendPoints[note.bendPoints.length - 1];
+                        let lastBendPoint: BendPoint = note.bendPoints![note.bendPoints!.length - 1];
                         endGlyphs.addGlyph(this.getBendNoteValue(note, lastBendPoint), lastBendPoint.value % 2 !== 0);
                     }
                 }
@@ -71,9 +71,9 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                         middleGlyphs.renderer = this.renderer;
                         this.BendNoteHeads.push(middleGlyphs);
                     }
-                    let middleBendPoint: BendPoint = note.bendPoints[1];
+                    let middleBendPoint: BendPoint = note.bendPoints![1];
                     middleGlyphs.addGlyph(
-                        this.getBendNoteValue(note, note.bendPoints[1]),
+                        this.getBendNoteValue(note, note.bendPoints![1]),
                         middleBendPoint.value % 2 !== 0
                     );
                     let endGlyphs = this._endNoteGlyph;
@@ -83,14 +83,14 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                         this._endNoteGlyph = endGlyphs;
                         this.BendNoteHeads.push(endGlyphs);
                     }
-                    let lastBendPoint: BendPoint = note.bendPoints[note.bendPoints.length - 1];
+                    let lastBendPoint: BendPoint = note.bendPoints![note.bendPoints!.length - 1];
                     endGlyphs.addGlyph(this.getBendNoteValue(note, lastBendPoint), lastBendPoint.value % 2 !== 0);
                 }
                 break;
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         // Draw note heads
         let startNoteRenderer: ScoreBarRenderer = this.renderer.scoreRenderer.layout!.getRendererForBar(
             this.renderer.staff.staveId,
@@ -222,7 +222,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                             startNoteRenderer.y +
                             startNoteRenderer.getScoreY(
                                 startNoteRenderer.accidentalHelper.getNoteLineForValue(
-                                    note.displayValue - ((note.bendPoints[0].value / 2) | 0),
+                                    note.displayValue - ((note.bendPoints![0].value / 2) | 0),
                                     false
                                 )
                             ) +
@@ -246,7 +246,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                 let endY: number = 0;
                 switch (note.bendType) {
                     case BendType.Bend:
-                        endValue = this.getBendNoteValue(note, note.bendPoints[note.bendPoints.length - 1]);
+                        endValue = this.getBendNoteValue(note, note.bendPoints![note.bendPoints!.length - 1]);
                         endY = this._endNoteGlyph!.getNoteValueY(endValue) + heightOffset;
                         this.drawBendSlur(
                             canvas,
@@ -260,7 +260,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                         );
                         break;
                     case BendType.BendRelease:
-                        let middleValue: number = this.getBendNoteValue(note, note.bendPoints[1]);
+                        let middleValue: number = this.getBendNoteValue(note, note.bendPoints![1]);
                         let middleY: number = this._middleNoteGlyph!.getNoteValueY(middleValue) + heightOffset;
                         this.drawBendSlur(
                             canvas,
@@ -272,7 +272,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                             this.scale,
                             slurText
                         );
-                        endValue = this.getBendNoteValue(note, note.bendPoints[note.bendPoints.length - 1]);
+                        endValue = this.getBendNoteValue(note, note.bendPoints![note.bendPoints!.length - 1]);
                         endY = this._endNoteGlyph!.getNoteValueY(endValue) + heightOffset;
                         this.drawBendSlur(
                             canvas,
@@ -287,7 +287,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                         break;
                     case BendType.Release:
                         if (this.BendNoteHeads.length > 0) {
-                            endValue = this.getBendNoteValue(note, note.bendPoints[note.bendPoints.length - 1]);
+                            endValue = this.getBendNoteValue(note, note.bendPoints![note.bendPoints!.length - 1]);
                             endY = this.BendNoteHeads[0].getNoteValueY(endValue) + heightOffset;
                             this.drawBendSlur(
                                 canvas,
@@ -313,7 +313,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                             startNoteRenderer.y +
                             startNoteRenderer.getScoreY(
                                 startNoteRenderer.accidentalHelper.getNoteLineForValue(
-                                    note.displayValue - ((note.bendPoints[0].value / 2) | 0),
+                                    note.displayValue - ((note.bendPoints![0].value / 2) | 0),
                                     false
                                 )
                             ) +
@@ -328,7 +328,7 @@ export class ScoreBendGlyph extends ScoreHelperNotesBaseGlyph {
                             this.scale
                         );
                         if (this.BendNoteHeads.length > 0) {
-                            endValue = this.getBendNoteValue(note, note.bendPoints[note.bendPoints.length - 1]);
+                            endValue = this.getBendNoteValue(note, note.bendPoints![note.bendPoints!.length - 1]);
                             endY = this.BendNoteHeads[0].getNoteValueY(endValue) + heightOffset;
                             this.drawBendSlur(
                                 canvas,
diff --git a/src/rendering/glyphs/ScoreBrushGlyph.ts b/src/rendering/glyphs/ScoreBrushGlyph.ts
index 97ddac879..99dd596ac 100644
--- a/src/rendering/glyphs/ScoreBrushGlyph.ts
+++ b/src/rendering/glyphs/ScoreBrushGlyph.ts
@@ -15,11 +15,11 @@ export class ScoreBrushGlyph extends Glyph {
         this._beat = beat;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 10 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let scoreBarRenderer: ScoreBarRenderer = this.renderer as ScoreBarRenderer;
         let lineSize: number = scoreBarRenderer.lineOffset;
         let startY: number = cy + this.y + (scoreBarRenderer.getNoteY(this._beat.maxNote!, NoteYPosition.Bottom) - lineSize);
diff --git a/src/rendering/glyphs/ScoreHelperNotesBaseGlyph.ts b/src/rendering/glyphs/ScoreHelperNotesBaseGlyph.ts
index bd717a19d..b8be64a1c 100644
--- a/src/rendering/glyphs/ScoreHelperNotesBaseGlyph.ts
+++ b/src/rendering/glyphs/ScoreHelperNotesBaseGlyph.ts
@@ -23,7 +23,7 @@ export class ScoreHelperNotesBaseGlyph extends Glyph {
         TieGlyph.drawBendSlur(canvas, x1, y1, x2, y2, down, scale, slurText);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.width = 0;
         for (let noteHeads of this.BendNoteHeads) {
diff --git a/src/rendering/glyphs/ScoreLegatoGlyph.ts b/src/rendering/glyphs/ScoreLegatoGlyph.ts
index 19e255670..4d46b79bb 100644
--- a/src/rendering/glyphs/ScoreLegatoGlyph.ts
+++ b/src/rendering/glyphs/ScoreLegatoGlyph.ts
@@ -12,11 +12,11 @@ export class ScoreLegatoGlyph extends TieGlyph {
         super(startBeat, endBeat, forEnd);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
     }
 
-    protected getBeamDirection(beat: Beat, noteRenderer: BarRendererBase): BeamDirection {
+    protected override getBeamDirection(beat: Beat, noteRenderer: BarRendererBase): BeamDirection {
         if (beat.isRest) {
             return BeamDirection.Up;
         }
@@ -29,7 +29,7 @@ export class ScoreLegatoGlyph extends TieGlyph {
         }
     }
 
-    protected getStartY(): number {
+    protected override getStartY(): number {
         if (this.startBeat!.isRest) {
             // below all lines
             return (this.startNoteRenderer as ScoreBarRenderer).getScoreY(9);
@@ -43,7 +43,7 @@ export class ScoreLegatoGlyph extends TieGlyph {
         }
     }
 
-    protected getEndY(): number {
+    protected override getEndY(): number {
         const endNoteScoreRenderer = this.endNoteRenderer as ScoreBarRenderer;
         if (this.endBeat!.isRest) {
             switch (this.tieDirection) {
@@ -89,11 +89,11 @@ export class ScoreLegatoGlyph extends TieGlyph {
         }
     }
 
-    protected getStartX(): number {
+    protected override getStartX(): number {
         return this.startNoteRenderer!.getBeatX(this.startBeat!, BeatXPosition.MiddleNotes);
     }
 
-    protected getEndX(): number {
+    protected override getEndX(): number {
         const endBeamDirection = (this.endNoteRenderer as ScoreBarRenderer).getBeatDirection(this.endBeat!);
         return this.endNoteRenderer!.getBeatX(
             this.endBeat!,
diff --git a/src/rendering/glyphs/ScoreNoteChordGlyph.ts b/src/rendering/glyphs/ScoreNoteChordGlyph.ts
index 8d654fa0b..bebb9df18 100644
--- a/src/rendering/glyphs/ScoreNoteChordGlyph.ts
+++ b/src/rendering/glyphs/ScoreNoteChordGlyph.ts
@@ -97,7 +97,7 @@ export class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         let direction: BeamDirection = this.direction;
         for (const effect of this.aboveBeatEffects.values()) {
@@ -150,7 +150,7 @@ export class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         // TODO: this method seems to be quite heavy according to the profiler, why?
         let scoreRenderer: ScoreBarRenderer = this.renderer as ScoreBarRenderer;
         //
diff --git a/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts b/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts
index 6f00bcf73..36351f360 100644
--- a/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts
+++ b/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts
@@ -43,7 +43,7 @@ export abstract class ScoreNoteChordGlyphBase extends Glyph {
         return !!this.maxNote && this.maxNote.line > 8;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this._infos.sort((a, b) => {
             return b.line - a.line;
         });
@@ -101,7 +101,7 @@ export abstract class ScoreNoteChordGlyphBase extends Glyph {
         this.width = w;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         cx += this.x;
         cy += this.y;
         // TODO: this method seems to be quite heavy according to the profiler, why?
diff --git a/src/rendering/glyphs/ScoreRestGlyph.ts b/src/rendering/glyphs/ScoreRestGlyph.ts
index dd7c52470..6fac3b44c 100644
--- a/src/rendering/glyphs/ScoreRestGlyph.ts
+++ b/src/rendering/glyphs/ScoreRestGlyph.ts
@@ -62,7 +62,7 @@ export class ScoreRestGlyph extends MusicFontGlyph {
         return 10;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = ScoreRestGlyph.getSize(this._duration) * this.scale;
     }
 
diff --git a/src/rendering/glyphs/ScoreSlideLineGlyph.ts b/src/rendering/glyphs/ScoreSlideLineGlyph.ts
index 608f40232..7807159f0 100644
--- a/src/rendering/glyphs/ScoreSlideLineGlyph.ts
+++ b/src/rendering/glyphs/ScoreSlideLineGlyph.ts
@@ -26,11 +26,11 @@ export class ScoreSlideLineGlyph extends Glyph {
         this._parent = parent;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 0;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         this.paintSlideIn(cx, cy, canvas);
         this.drawSlideOut(cx, cy, canvas);
     }
diff --git a/src/rendering/glyphs/ScoreSlurGlyph.ts b/src/rendering/glyphs/ScoreSlurGlyph.ts
index e56b737c1..3879c2cfe 100644
--- a/src/rendering/glyphs/ScoreSlurGlyph.ts
+++ b/src/rendering/glyphs/ScoreSlurGlyph.ts
@@ -16,11 +16,11 @@ export class ScoreSlurGlyph extends ScoreLegatoGlyph {
         this._endNote = endNote;
     }
 
-    protected getTieHeight(startX: number, startY: number, endX: number, endY: number): number {
+    protected override getTieHeight(startX: number, startY: number, endX: number, endY: number): number {
         return Math.log2(endX - startX + 1) * this.renderer.settings.notation.slurHeight;
     }
 
-    protected getStartY(): number {
+    protected override getStartY(): number {
         if (this.isStartCentered()) {
             switch (this.tieDirection) {
                 case BeamDirection.Up:
@@ -34,7 +34,7 @@ export class ScoreSlurGlyph extends ScoreLegatoGlyph {
         return this.startNoteRenderer!.getNoteY(this._startNote, NoteYPosition.Center);
     }
 
-    protected getEndY(): number {
+    protected override getEndY(): number {
         if (this.isEndCentered()) {
             if (this.isEndOnStem()) {
                 switch (this.tieDirection) {
@@ -78,13 +78,13 @@ export class ScoreSlurGlyph extends ScoreLegatoGlyph {
         return startBeamDirection !== endBeamDirection && this.startBeat!.graceType === GraceType.None;
     }
 
-    protected getStartX(): number {
+    protected override getStartX(): number {
         return this.isStartCentered()
             ? this.startNoteRenderer!.getBeatX(this._startNote.beat, BeatXPosition.MiddleNotes)
             : this.startNoteRenderer!.getNoteX(this._startNote, NoteXPosition.Right);
     }
 
-    protected getEndX(): number {
+    protected override getEndX(): number {
         if (this.isEndCentered()) {
             if (this.isEndOnStem()) {
                 return this.endNoteRenderer!.getBeatX(this._endNote.beat, BeatXPosition.Stem);
diff --git a/src/rendering/glyphs/ScoreTieGlyph.ts b/src/rendering/glyphs/ScoreTieGlyph.ts
index 195371ee2..4ac97223e 100644
--- a/src/rendering/glyphs/ScoreTieGlyph.ts
+++ b/src/rendering/glyphs/ScoreTieGlyph.ts
@@ -16,15 +16,15 @@ export class ScoreTieGlyph extends TieGlyph {
         this.endNote = endNote;
     }
 
-    protected shouldDrawBendSlur() {
+    protected override shouldDrawBendSlur() {
         return this.renderer.settings.notation.extendBendArrowsOnTiedNotes && !!this.startNote.bendOrigin && this.startNote.isTieOrigin;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
     }
 
-    protected getBeamDirection(beat: Beat, noteRenderer: BarRendererBase): BeamDirection {
+    protected override getBeamDirection(beat: Beat, noteRenderer: BarRendererBase): BeamDirection {
         // invert direction (if stems go up, ties go down to not cross them)
         switch ((noteRenderer as ScoreBarRenderer).getBeatDirection(beat)) {
             case BeamDirection.Up:
@@ -34,7 +34,7 @@ export class ScoreTieGlyph extends TieGlyph {
         }
     }
 
-    protected getStartY(): number {
+    protected override getStartY(): number {
         if (this.startBeat!.isRest) {
             // below all lines
             return (this.startNoteRenderer as ScoreBarRenderer).getScoreY(9);
@@ -48,7 +48,7 @@ export class ScoreTieGlyph extends TieGlyph {
         }
     }
 
-    protected getEndY(): number {
+    protected override getEndY(): number {
         const endNoteScoreRenderer = this.endNoteRenderer as ScoreBarRenderer;
         if (this.endBeat!.isRest) {
             switch (this.tieDirection) {
@@ -67,11 +67,11 @@ export class ScoreTieGlyph extends TieGlyph {
         }
     }
 
-    protected getStartX(): number {
+    protected override getStartX(): number {
         return this.startNoteRenderer!.getBeatX(this.startNote.beat, BeatXPosition.PostNotes);
     }
 
-    protected getEndX(): number {
+    protected override getEndX(): number {
         return this.endNoteRenderer!.getBeatX(this.endNote.beat, BeatXPosition.PreNotes);
     }
 }
diff --git a/src/rendering/glyphs/ScoreWhammyBarGlyph.ts b/src/rendering/glyphs/ScoreWhammyBarGlyph.ts
index cc5292591..dfb134dc1 100644
--- a/src/rendering/glyphs/ScoreWhammyBarGlyph.ts
+++ b/src/rendering/glyphs/ScoreWhammyBarGlyph.ts
@@ -27,7 +27,7 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
         this._beat = beat;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let whammyMode: NotationMode = this.renderer.settings.notation.notationMode;
         switch (this._beat.whammyBarType) {
             case WhammyType.None:
@@ -39,7 +39,7 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
                 {
                     let endGlyphs: BendNoteHeadGroupGlyph = new BendNoteHeadGroupGlyph(this._beat, false);
                     endGlyphs.renderer = this.renderer;
-                    let lastWhammyPoint: BendPoint = this._beat.whammyBarPoints[this._beat.whammyBarPoints.length - 1];
+                    let lastWhammyPoint: BendPoint = this._beat.whammyBarPoints![this._beat.whammyBarPoints!.length - 1];
                     for (let note of this._beat.notes) {
                         if (!note.isTieOrigin) {
                             endGlyphs.addGlyph(
@@ -64,10 +64,10 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
                         let middleGlyphs: BendNoteHeadGroupGlyph = new BendNoteHeadGroupGlyph(this._beat, false);
                         middleGlyphs.renderer = this.renderer;
                         if (this.renderer.settings.notation.notationMode === NotationMode.GuitarPro) {
-                            let middleBendPoint: BendPoint = this._beat.whammyBarPoints[1];
+                            let middleBendPoint: BendPoint = this._beat.whammyBarPoints![1];
                             for (let note of this._beat.notes) {
                                 middleGlyphs.addGlyph(
-                                    this.getBendNoteValue(note, this._beat.whammyBarPoints[1]),
+                                    this.getBendNoteValue(note, this._beat.whammyBarPoints![1]),
                                     middleBendPoint.value % 2 !== 0
                                 );
                             }
@@ -77,8 +77,8 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
                         let endGlyphs: BendNoteHeadGroupGlyph = new BendNoteHeadGroupGlyph(this._beat, false);
                         endGlyphs.renderer = this.renderer;
                         if (this.renderer.settings.notation.notationMode === NotationMode.GuitarPro) {
-                            let lastBendPoint: BendPoint = this._beat.whammyBarPoints[
-                                this._beat.whammyBarPoints.length - 1
+                            let lastBendPoint: BendPoint = this._beat.whammyBarPoints![
+                                this._beat.whammyBarPoints!.length - 1
                             ];
                             for (let note of this._beat.notes) {
                                 endGlyphs.addGlyph(
@@ -98,7 +98,7 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
         super.doLayout();
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let beat: Beat = this._beat;
         switch (beat.whammyBarType) {
             case WhammyType.None:
@@ -154,8 +154,8 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
             if (direction === BeamDirection.Up) {
                 heightOffset = -heightOffset;
             }
-            let endValue: number = beat.whammyBarPoints.length > 0 
-                ? this.getBendNoteValue(note, beat.whammyBarPoints[beat.whammyBarPoints.length - 1])
+            let endValue: number = beat.whammyBarPoints!.length > 0 
+                ? this.getBendNoteValue(note, beat.whammyBarPoints![beat.whammyBarPoints!.length - 1])
                 : 0;
             let endY: number = 0;
             let bendTie: boolean = false;
@@ -248,14 +248,14 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
                                 2 * this.scale;
                             let middleX: number = (simpleStartX + simpleEndX) / 2;
                             let text: string = (
-                                ((this._beat.whammyBarPoints[1].value - this._beat.whammyBarPoints[0].value) / 4) |
+                                ((this._beat.whammyBarPoints![1].value - this._beat.whammyBarPoints![0].value) / 4) |
                                 0
                             ).toString();
                             canvas.font = this.renderer.resources.tablatureFont;
                             canvas.fillText(text, middleX, cy + this.y);
                             let simpleStartY: number = cy + this.y + canvas.font.size + 2 * this.scale;
                             let simpleEndY: number = simpleStartY + ScoreWhammyBarGlyph.SimpleDipHeight * this.scale;
-                            if (this._beat.whammyBarPoints[1].value > this._beat.whammyBarPoints[0].value) {
+                            if (this._beat.whammyBarPoints![1].value > this._beat.whammyBarPoints![0].value) {
                                 canvas.moveTo(simpleStartX, simpleEndY);
                                 canvas.lineTo(middleX, simpleStartY);
                                 canvas.lineTo(simpleEndX, simpleEndY);
@@ -284,7 +284,7 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
                         this.BendNoteHeads[0].x = middleX - this.BendNoteHeads[0].noteHeadOffset;
                         this.BendNoteHeads[0].y = cy + startNoteRenderer.y;
                         this.BendNoteHeads[0].paint(0, 0, canvas);
-                        let middleValue: number = this.getBendNoteValue(note, beat.whammyBarPoints[1]);
+                        let middleValue: number = this.getBendNoteValue(note, beat.whammyBarPoints![1]);
                         let middleY: number = this.BendNoteHeads[0].getNoteValueY(middleValue) + heightOffset;
                         this.drawBendSlur(
                             canvas,
@@ -323,7 +323,7 @@ export class ScoreWhammyBarGlyph extends ScoreHelperNotesBaseGlyph {
                         startNoteRenderer.y +
                         startNoteRenderer.getScoreY(
                             startNoteRenderer.accidentalHelper.getNoteLineForValue(
-                                note.displayValue - ((note.beat.whammyBarPoints[0].value / 2) | 0),
+                                note.displayValue - ((note.beat.whammyBarPoints![0].value / 2) | 0),
                                 false
                             )
                         ) +
diff --git a/src/rendering/glyphs/TabBeatContainerGlyph.ts b/src/rendering/glyphs/TabBeatContainerGlyph.ts
index aa741e324..55914dc21 100644
--- a/src/rendering/glyphs/TabBeatContainerGlyph.ts
+++ b/src/rendering/glyphs/TabBeatContainerGlyph.ts
@@ -18,7 +18,7 @@ export class TabBeatContainerGlyph extends BeatContainerGlyph {
         super(beat, voiceContainer);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this._effectSlurs = [];
         super.doLayout();
         if (this._bend) {
@@ -28,7 +28,7 @@ export class TabBeatContainerGlyph extends BeatContainerGlyph {
         }
     }
 
-    protected createTies(n: Note): void {
+    protected override createTies(n: Note): void {
         if (!n.isVisible) {
             return;
         }
diff --git a/src/rendering/glyphs/TabBeatGlyph.ts b/src/rendering/glyphs/TabBeatGlyph.ts
index bdc1b534c..847baf082 100644
--- a/src/rendering/glyphs/TabBeatGlyph.ts
+++ b/src/rendering/glyphs/TabBeatGlyph.ts
@@ -19,21 +19,21 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase {
     public noteNumbers: TabNoteChordGlyph | null = null;
     public restGlyph: TabRestGlyph | null = null;
 
-    public getNoteX(note: Note, requestedPosition: NoteXPosition): number {
+    public override getNoteX(note: Note, requestedPosition: NoteXPosition): number {
         return this.noteNumbers ? this.noteNumbers.getNoteX(note, requestedPosition) : 0;
     }
     
-    public getNoteY(note: Note, requestedPosition: NoteYPosition): number {
+    public override getNoteY(note: Note, requestedPosition: NoteYPosition): number {
         return this.noteNumbers ? this.noteNumbers.getNoteY(note, requestedPosition) : 0;
     }
 
-    public buildBoundingsLookup(beatBounds:BeatBounds, cx:number, cy:number) {
+    public override buildBoundingsLookup(beatBounds:BeatBounds, cx:number, cy:number) {
         if(this.noteNumbers) {
             this.noteNumbers.buildBoundingsLookup(beatBounds, cx + this.x, cy + this.y);
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let tabRenderer: TabBarRenderer = this.renderer as TabBarRenderer;
         if (!this.container.beat.isRest) {
             //
@@ -136,7 +136,7 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase {
         }
     }
 
-    public updateBeamingHelper(): void {
+    public override updateBeamingHelper(): void {
         if (!this.container.beat.isRest) {
             this.noteNumbers!.updateBeamingHelper(this.container.x + this.x);
         } else {
diff --git a/src/rendering/glyphs/TabBeatPreNotesGlyph.ts b/src/rendering/glyphs/TabBeatPreNotesGlyph.ts
index c5bee21f0..7ed32ed93 100644
--- a/src/rendering/glyphs/TabBeatPreNotesGlyph.ts
+++ b/src/rendering/glyphs/TabBeatPreNotesGlyph.ts
@@ -4,7 +4,7 @@ import { SpacingGlyph } from '@src/rendering/glyphs/SpacingGlyph';
 import { TabBrushGlyph } from '@src/rendering/glyphs/TabBrushGlyph';
 
 export class TabBeatPreNotesGlyph extends BeatGlyphBase {
-    public doLayout(): void {
+    public override doLayout(): void {
         if (this.container.beat.brushType !== BrushType.None && !this.container.beat.isRest) {
             this.addGlyph(new TabBrushGlyph(this.container.beat));
             this.addGlyph(new SpacingGlyph(0, 0, 4 * this.scale));
diff --git a/src/rendering/glyphs/TabBendGlyph.ts b/src/rendering/glyphs/TabBendGlyph.ts
index be1839676..13f303869 100644
--- a/src/rendering/glyphs/TabBendGlyph.ts
+++ b/src/rendering/glyphs/TabBendGlyph.ts
@@ -122,7 +122,7 @@ export class TabBendGlyph extends Glyph {
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         let bendHeight: number = this._maxBendValue * TabBendGlyph.BendValueHeight * this.scale;
         this.renderer.registerOverflowTop(bendHeight);
@@ -182,14 +182,14 @@ export class TabBendGlyph extends Glyph {
         // though it might not be 100% correct from timing perspective.
         switch (note.bendType) {
             case BendType.Custom:
-                for (let bendPoint of note.bendPoints) {
+                for (let bendPoint of note.bendPoints!) {
                     renderingPoints.push(new TabBendRenderPoint(bendPoint.offset, bendPoint.value));
                 }
                 break;
             case BendType.BendRelease:
-                renderingPoints.push(new TabBendRenderPoint(0, note.bendPoints[0].value));
-                renderingPoints.push(new TabBendRenderPoint((BendPoint.MaxPosition / 2) | 0, note.bendPoints[1].value));
-                renderingPoints.push(new TabBendRenderPoint(BendPoint.MaxPosition, note.bendPoints[3].value));
+                renderingPoints.push(new TabBendRenderPoint(0, note.bendPoints![0].value));
+                renderingPoints.push(new TabBendRenderPoint((BendPoint.MaxPosition / 2) | 0, note.bendPoints![1].value));
+                renderingPoints.push(new TabBendRenderPoint(BendPoint.MaxPosition, note.bendPoints![3].value));
                 break;
             case BendType.Bend:
             case BendType.Hold:
@@ -197,14 +197,14 @@ export class TabBendGlyph extends Glyph {
             case BendType.PrebendBend:
             case BendType.PrebendRelease:
             case BendType.Release:
-                renderingPoints.push(new TabBendRenderPoint(0, note.bendPoints[0].value));
-                renderingPoints.push(new TabBendRenderPoint(BendPoint.MaxPosition, note.bendPoints[1].value));
+                renderingPoints.push(new TabBendRenderPoint(0, note.bendPoints![0].value));
+                renderingPoints.push(new TabBendRenderPoint(BendPoint.MaxPosition, note.bendPoints![1].value));
                 break;
         }
         return renderingPoints;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let color: Color = canvas.color;
         if (this._notes.length > 1) {
             canvas.color = this.renderer.resources.secondaryGlyphColor;
diff --git a/src/rendering/glyphs/TabBrushGlyph.ts b/src/rendering/glyphs/TabBrushGlyph.ts
index d846bffb1..99a10e6fa 100644
--- a/src/rendering/glyphs/TabBrushGlyph.ts
+++ b/src/rendering/glyphs/TabBrushGlyph.ts
@@ -15,11 +15,11 @@ export class TabBrushGlyph extends Glyph {
         this._beat = beat;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 10 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let tabBarRenderer: TabBarRenderer = this.renderer as TabBarRenderer;
         let startY: number =
             cy + this.x + (tabBarRenderer.getNoteY(this._beat.maxNote!, NoteYPosition.Top));
diff --git a/src/rendering/glyphs/TabClefGlyph.ts b/src/rendering/glyphs/TabClefGlyph.ts
index 096f9ce7a..d20f12934 100644
--- a/src/rendering/glyphs/TabClefGlyph.ts
+++ b/src/rendering/glyphs/TabClefGlyph.ts
@@ -7,11 +7,11 @@ export class TabClefGlyph extends Glyph {
         super(x, y);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 28 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let strings: number = this.renderer.bar.staff.tuning.length;
         let symbol: MusicFontSymbol = strings <= 4 ? MusicFontSymbol.FourStringTabClef : MusicFontSymbol.SixStringTabClef;
         let scale: number = strings <= 4 ? strings / 4.5 : strings / 6.5;
diff --git a/src/rendering/glyphs/TabNoteChordGlyph.ts b/src/rendering/glyphs/TabNoteChordGlyph.ts
index ef3d8f17e..2e2dc81bb 100644
--- a/src/rendering/glyphs/TabNoteChordGlyph.ts
+++ b/src/rendering/glyphs/TabNoteChordGlyph.ts
@@ -73,7 +73,7 @@ export class TabNoteChordGlyph extends Glyph {
         return 0;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         let w: number = 0;
         let noteStringWidth: number = 0;
         for (let i: number = 0, j: number = this._notes.length; i < j; i++) {
@@ -110,7 +110,7 @@ export class TabNoteChordGlyph extends Glyph {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         cx += this.x;
         cy += this.y;
         let res: RenderingResources = this.renderer.resources;
diff --git a/src/rendering/glyphs/TabRestGlyph.ts b/src/rendering/glyphs/TabRestGlyph.ts
index 1d01c9a08..856d01b2a 100644
--- a/src/rendering/glyphs/TabRestGlyph.ts
+++ b/src/rendering/glyphs/TabRestGlyph.ts
@@ -15,7 +15,7 @@ export class TabRestGlyph extends MusicFontGlyph {
         this._duration = duration;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         if (this._isVisibleRest) {
             this.width = ScoreRestGlyph.getSize(this._duration) * this.scale;
         } else {
@@ -33,7 +33,7 @@ export class TabRestGlyph extends MusicFontGlyph {
         }
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         if (this._isVisibleRest) {
             super.paint(cx, cy, canvas);
         }
diff --git a/src/rendering/glyphs/TabSlideLineGlyph.ts b/src/rendering/glyphs/TabSlideLineGlyph.ts
index 806b23fb2..44399ae98 100644
--- a/src/rendering/glyphs/TabSlideLineGlyph.ts
+++ b/src/rendering/glyphs/TabSlideLineGlyph.ts
@@ -24,11 +24,11 @@ export class TabSlideLineGlyph extends Glyph {
         this._parent = parent;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 0;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         this.paintSlideIn(cx, cy, canvas);
         this.paintSlideOut(cx, cy, canvas);
     }
diff --git a/src/rendering/glyphs/TabSlurGlyph.ts b/src/rendering/glyphs/TabSlurGlyph.ts
index 5f66be074..7460f1642 100644
--- a/src/rendering/glyphs/TabSlurGlyph.ts
+++ b/src/rendering/glyphs/TabSlurGlyph.ts
@@ -15,7 +15,7 @@ export class TabSlurGlyph extends TabTieGlyph {
         this._forSlide = forSlide;
     }
 
-    protected getTieHeight(startX: number, startY: number, endX: number, endY: number): number {
+    protected override getTieHeight(startX: number, startY: number, endX: number, endY: number): number {
         return Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight;
     }
 
@@ -64,7 +64,7 @@ export class TabSlurGlyph extends TabTieGlyph {
         return true;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let startNoteRenderer: BarRendererBase = this.renderer.scoreRenderer.layout!.getRendererForBar(
             this.renderer.staff.staveId,
             this.startBeat!.voice.bar
diff --git a/src/rendering/glyphs/TabTieGlyph.ts b/src/rendering/glyphs/TabTieGlyph.ts
index 77b79ad60..3a7e1270b 100644
--- a/src/rendering/glyphs/TabTieGlyph.ts
+++ b/src/rendering/glyphs/TabTieGlyph.ts
@@ -14,14 +14,14 @@ export class TabTieGlyph extends TieGlyph {
         this.endNote = endNote;
     }
 
-    protected getTieHeight(startX: number, startY: number, endX: number, endY: number): number {
+    protected override getTieHeight(startX: number, startY: number, endX: number, endY: number): number {
         if(this.startNote === this.endNote) {
             return 15;
         }
         return super.getTieHeight(startX, startY, endX, endY);
     }
 
-    protected getBeamDirection(beat: Beat, noteRenderer: BarRendererBase): BeamDirection {
+    protected override getBeamDirection(beat: Beat, noteRenderer: BarRendererBase): BeamDirection {
         if(this.startNote === this.endNote) {
             return BeamDirection.Up;
         }
@@ -32,7 +32,7 @@ export class TabTieGlyph extends TieGlyph {
         return note.string > 3 ? BeamDirection.Up : BeamDirection.Down;
     }
 
-    protected getStartY(): number {
+    protected override getStartY(): number {
         if(this.startNote === this.endNote) {
             return this.startNoteRenderer!.getNoteY(this.startNote, NoteYPosition.Center);
         }
@@ -43,18 +43,18 @@ export class TabTieGlyph extends TieGlyph {
         return this.startNoteRenderer!.getNoteY(this.startNote, NoteYPosition.Bottom);
     }
 
-    protected getEndY(): number {
+    protected override getEndY(): number {
         return this.getStartY();
     }
 
-    protected getStartX(): number {
+    protected override getStartX(): number {
         if(this.startNote === this.endNote) {
             return this.getEndX() - 20 * this.scale;
         }
         return this.startNoteRenderer!.getNoteX(this.startNote, NoteXPosition.Center);
     }
 
-    protected getEndX(): number {
+    protected override getEndX(): number {
         if(this.startNote === this.endNote) {
             return this.endNoteRenderer!.getNoteX(this.endNote, NoteXPosition.Left);
         }
diff --git a/src/rendering/glyphs/TabWhammyBarGlyph.ts b/src/rendering/glyphs/TabWhammyBarGlyph.ts
index e883bceb1..e67087d50 100644
--- a/src/rendering/glyphs/TabWhammyBarGlyph.ts
+++ b/src/rendering/glyphs/TabWhammyBarGlyph.ts
@@ -28,7 +28,7 @@ export class TabWhammyBarGlyph extends Glyph {
     private createRenderingPoints(beat: Beat): BendPoint[] {
         // advanced rendering
         if (beat.whammyBarType === WhammyType.Custom) {
-            return beat.whammyBarPoints;
+            return beat.whammyBarPoints!;
         }
         let renderingPoints: BendPoint[] = [];
         // Guitar Pro Rendering Note:
@@ -39,21 +39,21 @@ export class TabWhammyBarGlyph extends Glyph {
             case WhammyType.Hold:
             case WhammyType.PrediveDive:
             case WhammyType.Predive:
-                renderingPoints.push(new BendPoint(0, beat.whammyBarPoints[0].value));
-                renderingPoints.push(new BendPoint(BendPoint.MaxPosition, beat.whammyBarPoints[1].value));
+                renderingPoints.push(new BendPoint(0, beat.whammyBarPoints![0].value));
+                renderingPoints.push(new BendPoint(BendPoint.MaxPosition, beat.whammyBarPoints![1].value));
                 break;
             case WhammyType.Dip:
-                renderingPoints.push(new BendPoint(0, beat.whammyBarPoints[0].value));
-                renderingPoints.push(new BendPoint((BendPoint.MaxPosition / 2) | 0, beat.whammyBarPoints[1].value));
+                renderingPoints.push(new BendPoint(0, beat.whammyBarPoints![0].value));
+                renderingPoints.push(new BendPoint((BendPoint.MaxPosition / 2) | 0, beat.whammyBarPoints![1].value));
                 renderingPoints.push(
-                    new BendPoint(BendPoint.MaxPosition, beat.whammyBarPoints[beat.whammyBarPoints.length - 1].value)
+                    new BendPoint(BendPoint.MaxPosition, beat.whammyBarPoints![beat.whammyBarPoints!.length - 1].value)
                 );
                 break;
         }
         return renderingPoints;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this._isSimpleDip =
             this.renderer.settings.notation.notationMode === NotationMode.SongBook &&
@@ -75,7 +75,7 @@ export class TabWhammyBarGlyph extends Glyph {
         let topOffset: number = maxValue!.value > 0 ? Math.abs(this.getOffset(maxValue!.value)) : 0;
         if (
             topOffset > 0 ||
-            this._beat.whammyBarPoints[0].value !== 0 ||
+            this._beat.whammyBarPoints![0].value !== 0 ||
             this.renderer.settings.notation.isNotationElementVisible(NotationElement.ZerosOnDiveWhammys)
         ) {
             topOffset += this.renderer.resources.tablatureFont.size * 2;
@@ -104,7 +104,7 @@ export class TabWhammyBarGlyph extends Glyph {
         return offset;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let startNoteRenderer: BarRendererBase = this.renderer;
         let endBeat: Beat | null = this._beat.nextBeat;
         let endNoteRenderer: TabBarRenderer | null = null;
diff --git a/src/rendering/glyphs/TempoGlyph.ts b/src/rendering/glyphs/TempoGlyph.ts
index 00d7811fc..a7d7ca02f 100644
--- a/src/rendering/glyphs/TempoGlyph.ts
+++ b/src/rendering/glyphs/TempoGlyph.ts
@@ -12,12 +12,12 @@ export class TempoGlyph extends EffectGlyph {
         this._tempo = tempo;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = 25 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let res: RenderingResources = this.renderer.resources;
         canvas.font = res.markerFont;
         canvas.fillMusicFontSymbol(
diff --git a/src/rendering/glyphs/TextGlyph.ts b/src/rendering/glyphs/TextGlyph.ts
index ce8eb972a..1776fc629 100644
--- a/src/rendering/glyphs/TextGlyph.ts
+++ b/src/rendering/glyphs/TextGlyph.ts
@@ -15,12 +15,12 @@ export class TextGlyph extends EffectGlyph {
         this.textAlign = textAlign;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = this.font.size * this._lines.length;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let color = canvas.color ;
         canvas.color = color;
         canvas.font = this.font;
diff --git a/src/rendering/glyphs/TieGlyph.ts b/src/rendering/glyphs/TieGlyph.ts
index 8d88f6a66..ecd9a90fb 100644
--- a/src/rendering/glyphs/TieGlyph.ts
+++ b/src/rendering/glyphs/TieGlyph.ts
@@ -21,11 +21,11 @@ export class TieGlyph extends Glyph {
         this.forEnd = forEnd;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 0;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         if (!this.endBeat) {
             return;
         }
diff --git a/src/rendering/glyphs/TimeSignatureGlyph.ts b/src/rendering/glyphs/TimeSignatureGlyph.ts
index 6d3260b5f..d59385b56 100644
--- a/src/rendering/glyphs/TimeSignatureGlyph.ts
+++ b/src/rendering/glyphs/TimeSignatureGlyph.ts
@@ -19,7 +19,7 @@ export abstract class TimeSignatureGlyph extends GlyphGroup {
     protected abstract get commonScale(): number;
     protected abstract get numberScale(): number;
 
-    public doLayout(): void {
+    public override doLayout(): void {
         if (this._isCommon && this._numerator === 2 && this._denominator === 2) {
             let common: MusicFontGlyph = new MusicFontGlyph(
                 0,
diff --git a/src/rendering/glyphs/TremoloPickingGlyph.ts b/src/rendering/glyphs/TremoloPickingGlyph.ts
index f295cc39a..0b6c86e22 100644
--- a/src/rendering/glyphs/TremoloPickingGlyph.ts
+++ b/src/rendering/glyphs/TremoloPickingGlyph.ts
@@ -7,7 +7,7 @@ export class TremoloPickingGlyph extends MusicFontGlyph {
         super(x, y, 1, TremoloPickingGlyph.getSymbol(duration));
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         this.width = 12 * this.scale;
     }
 
diff --git a/src/rendering/glyphs/TrillGlyph.ts b/src/rendering/glyphs/TrillGlyph.ts
index cf92c7f31..20957716f 100644
--- a/src/rendering/glyphs/TrillGlyph.ts
+++ b/src/rendering/glyphs/TrillGlyph.ts
@@ -8,12 +8,12 @@ export class TrillGlyph extends EffectGlyph {
         super(x, y);
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = 20 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         let res: RenderingResources = this.renderer.resources;
         canvas.font = res.markerFont;
         let textw: number = canvas.measureText('tr');
diff --git a/src/rendering/glyphs/TripletFeelGlyph.ts b/src/rendering/glyphs/TripletFeelGlyph.ts
index 8b6651154..be6b7839e 100644
--- a/src/rendering/glyphs/TripletFeelGlyph.ts
+++ b/src/rendering/glyphs/TripletFeelGlyph.ts
@@ -25,12 +25,12 @@ export class TripletFeelGlyph extends EffectGlyph {
         this._tripletFeel = tripletFeel;
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
         super.doLayout();
         this.height = 25 * this.scale;
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         cx += this.x;
         cy += this.y;
         let noteY: number = cy + this.height * NoteHeadGlyph.GraceScale;
diff --git a/src/rendering/glyphs/TuningGlyph.ts b/src/rendering/glyphs/TuningGlyph.ts
index 9c8a43199..13424e682 100644
--- a/src/rendering/glyphs/TuningGlyph.ts
+++ b/src/rendering/glyphs/TuningGlyph.ts
@@ -16,7 +16,7 @@ export class TuningGlyph extends GlyphGroup {
         this.glyphs = [];
     }
 
-    public doLayout() {
+    public override doLayout() {
         if (this.glyphs!.length > 0) {
             return;
         }
diff --git a/src/rendering/glyphs/VoiceContainerGlyph.ts b/src/rendering/glyphs/VoiceContainerGlyph.ts
index a135e3d0d..6eabc93ec 100644
--- a/src/rendering/glyphs/VoiceContainerGlyph.ts
+++ b/src/rendering/glyphs/VoiceContainerGlyph.ts
@@ -119,7 +119,7 @@ export class VoiceContainerGlyph extends GlyphGroup {
         this.scaleToForce(Math.max(this.renderer.settings.display.stretchForce, info.minStretchForce));
     }
 
-    public addGlyph(g: Glyph): void {
+    public override addGlyph(g: Glyph): void {
         let bg: BeatContainerGlyph = g as BeatContainerGlyph;
         g.x =
             this.beatGlyphs.length === 0
@@ -134,10 +134,10 @@ export class VoiceContainerGlyph extends GlyphGroup {
         }
     }
 
-    public doLayout(): void {
+    public override doLayout(): void {
     }
 
-    public paint(cx: number, cy: number, canvas: ICanvas): void {
+    public override paint(cx: number, cy: number, canvas: ICanvas): void {
         // canvas.color = Color.random();
         // canvas.strokeRect(cx + this.x, cy + this.y, this.width, this.renderer.height);
         canvas.color =
diff --git a/src/rendering/layout/HorizontalScreenLayout.ts b/src/rendering/layout/HorizontalScreenLayout.ts
index d6bdcd6f4..924d5f3d5 100644
--- a/src/rendering/layout/HorizontalScreenLayout.ts
+++ b/src/rendering/layout/HorizontalScreenLayout.ts
@@ -33,10 +33,6 @@ export class HorizontalScreenLayout extends ScoreLayout {
         return false;
     }
 
-    public get padding(): number[] {
-        return this._pagePadding!;
-    }
-
     public get firstBarX(): number{
         let x=  this._pagePadding![0];
         if(this._group) {
diff --git a/src/rendering/layout/PageViewLayout.ts b/src/rendering/layout/PageViewLayout.ts
index 7a09eef78..1acdf322c 100644
--- a/src/rendering/layout/PageViewLayout.ts
+++ b/src/rendering/layout/PageViewLayout.ts
@@ -73,10 +73,6 @@ export class PageViewLayout extends ScoreLayout {
         return true;
     }
 
-    public get padding(): number[] {
-        return this._pagePadding!;
-    }
-
     public get firstBarX(): number {
         let x = this._pagePadding![0];
         if (this._groups.length > 0) {
diff --git a/src/rendering/layout/ScoreLayout.ts b/src/rendering/layout/ScoreLayout.ts
index 7d1e2a178..982b84dcb 100644
--- a/src/rendering/layout/ScoreLayout.ts
+++ b/src/rendering/layout/ScoreLayout.ts
@@ -50,7 +50,6 @@ export abstract class ScoreLayout {
         this.renderer = renderer;
     }
 
-    public abstract get padding(): number[];
     public abstract get firstBarX(): number;
     public abstract get supportsResize(): boolean;
 
@@ -191,11 +190,14 @@ export abstract class ScoreLayout {
             let chords: Map<string, Chord> = new Map<string, Chord>();
             for (let track of this.renderer.tracks!) {
                 for (let staff of track.staves) {
-                    for (const [chordId, chord] of staff.chords) {
-                        if (!chords.has(chordId)) {
-                            if (chord.showDiagram) {
-                                chords.set(chordId, chord);
-                                this.chordDiagrams!.addChord(chord);
+                    const sc = staff.chords;
+                    if (sc) {
+                        for (const [chordId, chord] of sc) {
+                            if (!chords.has(chordId)) {
+                                if (chord.showDiagram) {
+                                    chords.set(chordId, chord);
+                                    this.chordDiagrams!.addChord(chord);
+                                }
                             }
                         }
                     }
diff --git a/src/xml/XmlDocument.ts b/src/xml/XmlDocument.ts
index 769b95986..e4d3d1667 100644
--- a/src/xml/XmlDocument.ts
+++ b/src/xml/XmlDocument.ts
@@ -35,7 +35,7 @@ export class XmlDocument extends XmlNode {
         XmlParser.parse(xml, 0, this);
     }
 
-    public toString() {
+    public override toString() {
         return this.toFormattedString();
     }
 
diff --git a/test/audio/FlatMidiEventGenerator.ts b/test/audio/FlatMidiEventGenerator.ts
index 9c1a4eefd..f2c90ee5b 100644
--- a/test/audio/FlatMidiEventGenerator.ts
+++ b/test/audio/FlatMidiEventGenerator.ts
@@ -101,11 +101,11 @@ export class TempoEvent extends FlatMidiEvent {
         this.tempo = tempo;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `Tempo: ${super.toString()} Tempo[${this.tempo}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -128,11 +128,11 @@ export class TimeSignatureEvent extends FlatMidiEvent {
         this.denominator = denominator;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `TimeSignature: ${super.toString()} Numerator[${this.numerator}] Denominator[${this.denominator}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -153,11 +153,11 @@ export class TrackMidiEvent extends FlatMidiEvent {
         this.track = track;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `${super.toString()} Track[${this.track}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -175,7 +175,7 @@ export class TrackEndEvent extends TrackMidiEvent {
         super(tick, track);
     }
 
-    public toString(): string {
+    public override toString(): string {
         return 'End of Track ' + super.toString();
     }
 }
@@ -188,11 +188,11 @@ export class ChannelMidiEvent extends TrackMidiEvent {
         this.channel = channel;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `${super.toString()} Channel[${this.channel}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -215,11 +215,11 @@ export class ControlChangeEvent extends ChannelMidiEvent {
         this.value = value;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `ControlChange: ${super.toString()} Controller[${this.controller}] Value[${this.value}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -237,11 +237,11 @@ export class RestEvent extends ChannelMidiEvent {
         super(tick, track, channel);
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `Rest: ${super.toString()}`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -261,11 +261,11 @@ export class ProgramChangeEvent extends ChannelMidiEvent {
         this.program = program;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `ProgramChange: ${super.toString()} Program[${this.program}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -297,11 +297,11 @@ export class NoteEvent extends ChannelMidiEvent {
         this.dynamicValue = dynamicValue;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `Note: ${super.toString()} Length[${this.length}] Key[${this.key}] Dynamic[${this.dynamicValue}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -322,11 +322,11 @@ export class BendEvent extends ChannelMidiEvent {
         this.value = value;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `Bend: ${super.toString()} Value[${this.value}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
@@ -348,11 +348,11 @@ export class NoteBendEvent extends ChannelMidiEvent {
         this.value = value;
     }
 
-    public toString(): string {
+    public override toString(): string {
         return `NoteBend: ${super.toString()} Key[${this.key}] Value[${this.value}]`;
     }
 
-    public equals(obj: unknown): boolean {
+    public override equals(obj: unknown): boolean {
         if (!super.equals(obj)) {
             return false;
         }
diff --git a/test/exporter/Gp7Exporter.test.ts b/test/exporter/Gp7Exporter.test.ts
index 5b0f551ea..eb1245645 100644
--- a/test/exporter/Gp7Exporter.test.ts
+++ b/test/exporter/Gp7Exporter.test.ts
@@ -99,17 +99,17 @@ describe('Gp7ExporterTest', () => {
 
     it('gp5-to-gp7', async () => {
         await testRoundTripEqual(`conversion/full-song.gp5`, [
-            'accidentalMode', // gets upgraded from default
-            'percussionArticulations', // gets added
+            'accidentalmode', // gets upgraded from default
+            'percussionarticulations', // gets added
             'automations' // volume automations are not yet supported in gpif
         ]);
     });
 
     it('gp6-to-gp7', async () => {
         await testRoundTripEqual(`conversion/full-song.gpx`, [
-            'accidentalMode', // gets upgraded from default
-            'percussionArticulations', // gets added
-            'percussionArticulation', // gets added
+            'accidentalmode', // gets upgraded from default
+            'percussionarticulations', // gets added
+            'percussionarticulation', // gets added
         ]);
     });
 
@@ -138,6 +138,6 @@ describe('Gp7ExporterTest', () => {
         const expectedJson = JsonConverter.scoreToJsObject(expected);
         const actualJson = JsonConverter.scoreToJsObject(actual)
 
-        ComparisonHelpers.expectJsonEqual(expectedJson, actualJson, '<alphatex>', ['accidentalMode']);
+        ComparisonHelpers.expectJsonEqual(expectedJson, actualJson, '<alphatex>', ['accidentalmode']);
     });
 });
diff --git a/test/importer/Gp7Importer.test.ts b/test/importer/Gp7Importer.test.ts
index 5183c781f..5cd2375d5 100644
--- a/test/importer/Gp7Importer.test.ts
+++ b/test/importer/Gp7Importer.test.ts
@@ -95,27 +95,27 @@ describe('Gp7ImporterTest', () => {
         const reader = await prepareGp7ImporterWithFile('guitarpro7/bends.gp');
         let score: Score = reader.readScore();
         expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendType).toEqual(BendType.Bend);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints.length).toEqual(2);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[0].offset).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[0].value).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[1].offset).toEqual(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[1].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints!.length).toEqual(2);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![0].offset).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![1].offset).toEqual(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![1].value).toEqual(4);
         expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendType).toEqual(BendType.Bend);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints.length).toEqual(2);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[0].offset).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[0].value).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[1].offset).toEqual(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[1].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints!.length).toEqual(2);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![0].offset).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![1].offset).toEqual(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![1].value).toEqual(4);
         expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendType).toEqual(BendType.BendRelease);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints.length).toEqual(4);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[0].offset).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[0].value).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[1].offset).toEqual(30);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[1].value).toEqual(12);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[2].offset).toEqual(30);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[2].value).toEqual(12);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[3].offset).toEqual(60);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[3].value).toEqual(6);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints!.length).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![0].offset).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![1].offset).toEqual(30);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![1].value).toEqual(12);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![2].offset).toEqual(30);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![2].value).toEqual(12);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![3].offset).toEqual(60);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![3].value).toEqual(6);
     });
 
     it('bends-advanced', async () => {
@@ -127,203 +127,203 @@ describe('Gp7ImporterTest', () => {
         // // Bar 1
         let note: Note = score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0];
         expect(note.bendType).toEqual(BendType.BendRelease);
-        expect(note.bendPoints.length).toEqual(4);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(10.2);
-        expect(note.bendPoints[1].value).toEqual(4);
-        expect(note.bendPoints[2].offset).toBeCloseTo(20.4);
-        expect(note.bendPoints[2].value).toEqual(4);
-        expect(note.bendPoints[3].offset).toBeCloseTo(30);
-        expect(note.bendPoints[3].value).toEqual(0);
+        expect(note.bendPoints!.length).toEqual(4);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(10.2);
+        expect(note.bendPoints![1].value).toEqual(4);
+        expect(note.bendPoints![2].offset).toBeCloseTo(20.4);
+        expect(note.bendPoints![2].value).toEqual(4);
+        expect(note.bendPoints![3].offset).toBeCloseTo(30);
+        expect(note.bendPoints![3].value).toEqual(0);
 
         // // Bar 2
         note = score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(59.4);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(59.4);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[1].voices[0].beats[1].notes[0];
         expect(note.bendType).toEqual(BendType.BendRelease);
-        expect(note.bendPoints.length).toEqual(4);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(10.2);
-        expect(note.bendPoints[1].value).toEqual(4);
-        expect(note.bendPoints[2].offset).toBeCloseTo(45.6);
-        expect(note.bendPoints[2].value).toEqual(4);
-        expect(note.bendPoints[3].offset).toBeCloseTo(59.4);
-        expect(note.bendPoints[3].value).toEqual(0);
+        expect(note.bendPoints!.length).toEqual(4);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(10.2);
+        expect(note.bendPoints![1].value).toEqual(4);
+        expect(note.bendPoints![2].offset).toBeCloseTo(45.6);
+        expect(note.bendPoints![2].value).toEqual(4);
+        expect(note.bendPoints![3].offset).toBeCloseTo(59.4);
+        expect(note.bendPoints![3].value).toEqual(0);
 
         // // Bar 3
         note = score.tracks[0].staves[0].bars[2].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.Prebend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(4);
-        expect(note.bendPoints[1].offset).toBeCloseTo(60);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(4);
+        expect(note.bendPoints![1].offset).toBeCloseTo(60);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[2].voices[0].beats[1].notes[0];
         expect(note.bendType).toEqual(BendType.PrebendBend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(4);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(6);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(4);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(6);
 
         // // Bar 4
         note = score.tracks[0].staves[0].bars[3].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.PrebendRelease);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(4);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(0);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(4);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(0);
 
         // // Bar 5
         note = score.tracks[0].staves[0].bars[4].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(14.4);
-        expect(note.bendPoints[1].value).toEqual(8);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(14.4);
+        expect(note.bendPoints![1].value).toEqual(8);
 
         note = score.tracks[0].staves[0].bars[4].voices[0].beats[1].notes[0];
         expect(note.bendType).toEqual(BendType.BendRelease);
-        expect(note.bendPoints.length).toEqual(4);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(9);
-        expect(note.bendPoints[1].value).toEqual(8);
-        expect(note.bendPoints[2].offset).toBeCloseTo(20.4);
-        expect(note.bendPoints[2].value).toEqual(8);
-        expect(note.bendPoints[3].offset).toBeCloseTo(31.2);
-        expect(note.bendPoints[3].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(4);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(9);
+        expect(note.bendPoints![1].value).toEqual(8);
+        expect(note.bendPoints![2].offset).toBeCloseTo(20.4);
+        expect(note.bendPoints![2].value).toEqual(8);
+        expect(note.bendPoints![3].offset).toBeCloseTo(31.2);
+        expect(note.bendPoints![3].value).toEqual(4);
 
         // // Bar 6
         note = score.tracks[0].staves[0].bars[5].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.Prebend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(8);
-        expect(note.bendPoints[1].offset).toBeCloseTo(60);
-        expect(note.bendPoints[1].value).toEqual(8);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(8);
+        expect(note.bendPoints![1].offset).toBeCloseTo(60);
+        expect(note.bendPoints![1].value).toEqual(8);
 
         note = score.tracks[0].staves[0].bars[5].voices[0].beats[1].notes[0];
         expect(note.bendType).toEqual(BendType.PrebendBend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(8);
-        expect(note.bendPoints[1].offset).toBeCloseTo(16.2);
-        expect(note.bendPoints[1].value).toEqual(12);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(8);
+        expect(note.bendPoints![1].offset).toBeCloseTo(16.2);
+        expect(note.bendPoints![1].value).toEqual(12);
 
         // // Bar 7
         note = score.tracks[0].staves[0].bars[6].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.PrebendRelease);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(8);
-        expect(note.bendPoints[1].offset).toBeCloseTo(14.4);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(8);
+        expect(note.bendPoints![1].offset).toBeCloseTo(14.4);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         // // Bar 8
         note = score.tracks[0].staves[0].bars[7].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         // // Bar 9
         note = score.tracks[0].staves[0].bars[8].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.BendRelease);
-        expect(note.bendPoints.length).toEqual(4);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(10.2);
-        expect(note.bendPoints[1].value).toEqual(4);
-        expect(note.bendPoints[2].offset).toBeCloseTo(20.4);
-        expect(note.bendPoints[2].value).toEqual(4);
-        expect(note.bendPoints[3].offset).toBeCloseTo(30);
-        expect(note.bendPoints[3].value).toEqual(0);
+        expect(note.bendPoints!.length).toEqual(4);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(10.2);
+        expect(note.bendPoints![1].value).toEqual(4);
+        expect(note.bendPoints![2].offset).toBeCloseTo(20.4);
+        expect(note.bendPoints![2].value).toEqual(4);
+        expect(note.bendPoints![3].offset).toBeCloseTo(30);
+        expect(note.bendPoints![3].value).toEqual(0);
         // Combined Bends
 
         // // Bar 10
         note = score.tracks[0].staves[0].bars[9].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[9].voices[0].beats[1].notes[0];
         expect(note.bendType).toEqual(BendType.Release);
         expect(note.isContinuedBend).toBe(true);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(4);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(0);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(4);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(0);
 
         note = score.tracks[0].staves[0].bars[9].voices[0].beats[2].notes[0];
         expect(note.bendType).toEqual(BendType.Bend);
         expect(note.isContinuedBend).toBe(false);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         // // Bar 11
         note = score.tracks[0].staves[0].bars[10].voices[0].beats[0].notes[0];
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[10].voices[0].beats[1].notes[0];
         expect(note.bendType).toEqual(BendType.Bend);
         expect(note.isContinuedBend).toBe(true);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(4);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(8);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(4);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(8);
 
         note = score.tracks[0].staves[0].bars[10].voices[0].beats[2].notes[0];
         expect(note.bendType).toEqual(BendType.Release);
         expect(note.isContinuedBend).toBe(true);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(8);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(8);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[10].voices[0].beats[3].notes[0];
         expect(note.bendType).toEqual(BendType.Release);
         expect(note.isContinuedBend).toBe(true);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(4);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(0);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(4);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(0);
 
         // Grace Bends
 
@@ -331,70 +331,70 @@ describe('Gp7ImporterTest', () => {
         note = score.tracks[0].staves[0].bars[11].voices[0].beats[0].notes[0];
         expect(note.beat.graceType).toEqual(GraceType.BeforeBeat);
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         // // Bar 13
         note = score.tracks[0].staves[0].bars[12].voices[0].beats[0].notes[0];
         expect(note.beat.graceType).toEqual(GraceType.BeforeBeat);
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[12].voices[0].beats[1].notes[0];
         expect(note.isContinuedBend).toBe(true);
         expect(note.bendType).toEqual(BendType.Hold);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(4);
-        expect(note.bendPoints[1].offset).toBeCloseTo(60);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(4);
+        expect(note.bendPoints![1].offset).toBeCloseTo(60);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         // // Bar 14
         note = score.tracks[0].staves[0].bars[13].voices[0].beats[0].notes[0];
         expect(note.beat.graceType).toEqual(GraceType.OnBeat);
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(18);
-        expect(note.bendPoints[1].value).toEqual(1);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(18);
+        expect(note.bendPoints![1].value).toEqual(1);
 
         note = score.tracks[0].staves[0].bars[13].voices[0].beats[1].notes[0];
         expect(note.isContinuedBend).toBe(true);
         expect(note.bendType).toEqual(BendType.Hold);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(1);
-        expect(note.bendPoints[1].offset).toBeCloseTo(60);
-        expect(note.bendPoints[1].value).toEqual(1);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(1);
+        expect(note.bendPoints![1].offset).toBeCloseTo(60);
+        expect(note.bendPoints![1].value).toEqual(1);
 
         // // Bar 15
         note = score.tracks[0].staves[0].bars[14].voices[0].beats[0].notes[0];
         expect(note.beat.graceType).toEqual(GraceType.BeforeBeat);
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[14].voices[0].beats[1].notes[0];
         expect(note.fret).toEqual(12);
         expect(note.isTieDestination).toBe(true);
         expect(note.isContinuedBend).toBe(true);
         expect(note.bendType).toEqual(BendType.Hold);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(4);
-        expect(note.bendPoints[1].offset).toBeCloseTo(60);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(4);
+        expect(note.bendPoints![1].offset).toBeCloseTo(60);
+        expect(note.bendPoints![1].value).toEqual(4);
 
         note = score.tracks[0].staves[0].bars[14].voices[0].beats[1].notes[1];
         expect(note.fret).toEqual(10);
@@ -408,11 +408,11 @@ describe('Gp7ImporterTest', () => {
         // // Bar 16
         note = score.tracks[0].staves[0].bars[15].voices[0].beats[0].notes[1];
         expect(note.bendType).toEqual(BendType.Bend);
-        expect(note.bendPoints.length).toEqual(2);
-        expect(note.bendPoints[0].offset).toBeCloseTo(0);
-        expect(note.bendPoints[0].value).toEqual(0);
-        expect(note.bendPoints[1].offset).toBeCloseTo(15);
-        expect(note.bendPoints[1].value).toEqual(4);
+        expect(note.bendPoints!.length).toEqual(2);
+        expect(note.bendPoints![0].offset).toBeCloseTo(0);
+        expect(note.bendPoints![0].value).toEqual(0);
+        expect(note.bendPoints![1].offset).toBeCloseTo(15);
+        expect(note.bendPoints![1].value).toEqual(4);
     });
 
     it('whammy-advanced', async () => {
@@ -422,207 +422,207 @@ describe('Gp7ImporterTest', () => {
         // Bar 1
         let beat: Beat = score.tracks[0].staves[0].bars[0].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.Dive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(0);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(45);
-        expect(beat.whammyBarPoints[1].value).toEqual(-4);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(0);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(45);
+        expect(beat.whammyBarPoints![1].value).toEqual(-4);
 
         beat = score.tracks[0].staves[0].bars[0].voices[0].beats[2];
         expect(beat.whammyBarType).toEqual(WhammyType.PrediveDive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(-4);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(60);
-        expect(beat.whammyBarPoints[1].value).toEqual(-16);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(-4);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(60);
+        expect(beat.whammyBarPoints![1].value).toEqual(-16);
 
         // Bar 2
         beat = score.tracks[0].staves[0].bars[1].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.Dip);
-        expect(beat.whammyBarPoints.length).toEqual(3);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(0);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(15);
-        expect(beat.whammyBarPoints[1].value).toEqual(-16);
-        expect(beat.whammyBarPoints[2].offset).toBeCloseTo(30);
-        expect(beat.whammyBarPoints[2].value).toEqual(0);
+        expect(beat.whammyBarPoints!.length).toEqual(3);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(0);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(15);
+        expect(beat.whammyBarPoints![1].value).toEqual(-16);
+        expect(beat.whammyBarPoints![2].offset).toBeCloseTo(30);
+        expect(beat.whammyBarPoints![2].value).toEqual(0);
 
         beat = score.tracks[0].staves[0].bars[1].voices[0].beats[2];
         expect(beat.whammyBarType).toEqual(WhammyType.Dip);
-        expect(beat.whammyBarPoints.length).toEqual(4);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(0);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(14.4);
-        expect(beat.whammyBarPoints[1].value).toEqual(-12);
-        expect(beat.whammyBarPoints[2].offset).toBeCloseTo(31.8);
-        expect(beat.whammyBarPoints[2].value).toEqual(-12);
-        expect(beat.whammyBarPoints[3].offset).toBeCloseTo(53.4);
-        expect(beat.whammyBarPoints[3].value).toEqual(0);
+        expect(beat.whammyBarPoints!.length).toEqual(4);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(0);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(14.4);
+        expect(beat.whammyBarPoints![1].value).toEqual(-12);
+        expect(beat.whammyBarPoints![2].offset).toBeCloseTo(31.8);
+        expect(beat.whammyBarPoints![2].value).toEqual(-12);
+        expect(beat.whammyBarPoints![3].offset).toBeCloseTo(53.4);
+        expect(beat.whammyBarPoints![3].value).toEqual(0);
 
         // Bar 3
         beat = score.tracks[0].staves[0].bars[2].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.Dip);
-        expect(beat.whammyBarPoints.length).toEqual(3);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(0);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(15);
-        expect(beat.whammyBarPoints[1].value).toEqual(-16);
-        expect(beat.whammyBarPoints[2].offset).toBeCloseTo(30);
-        expect(beat.whammyBarPoints[2].value).toEqual(0);
+        expect(beat.whammyBarPoints!.length).toEqual(3);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(0);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(15);
+        expect(beat.whammyBarPoints![1].value).toEqual(-16);
+        expect(beat.whammyBarPoints![2].offset).toBeCloseTo(30);
+        expect(beat.whammyBarPoints![2].value).toEqual(0);
 
         beat = score.tracks[0].staves[0].bars[2].voices[0].beats[2];
         expect(beat.whammyBarType).toEqual(WhammyType.Dip);
-        expect(beat.whammyBarPoints.length).toEqual(4);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(0);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(14.4);
-        expect(beat.whammyBarPoints[1].value).toEqual(-12);
-        expect(beat.whammyBarPoints[2].offset).toBeCloseTo(31.8);
-        expect(beat.whammyBarPoints[2].value).toEqual(-12);
-        expect(beat.whammyBarPoints[3].offset).toBeCloseTo(53.4);
-        expect(beat.whammyBarPoints[3].value).toEqual(0);
+        expect(beat.whammyBarPoints!.length).toEqual(4);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(0);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(14.4);
+        expect(beat.whammyBarPoints![1].value).toEqual(-12);
+        expect(beat.whammyBarPoints![2].offset).toBeCloseTo(31.8);
+        expect(beat.whammyBarPoints![2].value).toEqual(-12);
+        expect(beat.whammyBarPoints![3].offset).toBeCloseTo(53.4);
+        expect(beat.whammyBarPoints![3].value).toEqual(0);
 
         // Bar 4
         beat = score.tracks[0].staves[0].bars[3].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.Predive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(-8);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(60);
-        expect(beat.whammyBarPoints[1].value).toEqual(-8);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(-8);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(60);
+        expect(beat.whammyBarPoints![1].value).toEqual(-8);
 
         // Bar 5
         beat = score.tracks[0].staves[0].bars[4].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.PrediveDive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(-4);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(30);
-        expect(beat.whammyBarPoints[1].value).toEqual(0);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(-4);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(30);
+        expect(beat.whammyBarPoints![1].value).toEqual(0);
 
         // Bar 6
         beat = score.tracks[0].staves[0].bars[5].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.PrediveDive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(-4);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(29.4);
-        expect(beat.whammyBarPoints[1].value).toEqual(-12);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(-4);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(29.4);
+        expect(beat.whammyBarPoints![1].value).toEqual(-12);
 
         beat = score.tracks[0].staves[0].bars[5].voices[0].beats[1];
         expect(beat.whammyBarType).toEqual(WhammyType.Dive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(-12);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(45.6);
-        expect(beat.whammyBarPoints[1].value).toEqual(0);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(-12);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(45.6);
+        expect(beat.whammyBarPoints![1].value).toEqual(0);
 
         // Bar 7
         beat = score.tracks[0].staves[0].bars[6].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.Dive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(0);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(45);
-        expect(beat.whammyBarPoints[1].value).toEqual(-4);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(0);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(45);
+        expect(beat.whammyBarPoints![1].value).toEqual(-4);
 
         beat = score.tracks[0].staves[0].bars[6].voices[0].beats[1];
         expect(beat.whammyBarType).toEqual(WhammyType.Hold);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(-4);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(60);
-        expect(beat.whammyBarPoints[1].value).toEqual(-4);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(-4);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(60);
+        expect(beat.whammyBarPoints![1].value).toEqual(-4);
 
         // Bar 8
         beat = score.tracks[0].staves[0].bars[7].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.Dive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(-4);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(46.2);
-        expect(beat.whammyBarPoints[1].value).toEqual(-12);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(-4);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(46.2);
+        expect(beat.whammyBarPoints![1].value).toEqual(-12);
 
         beat = score.tracks[0].staves[0].bars[7].voices[0].beats[1];
         expect(beat.whammyBarType).toEqual(WhammyType.Dive);
-        expect(beat.whammyBarPoints.length).toEqual(2);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(-12);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(44.4);
-        expect(beat.whammyBarPoints[1].value).toEqual(8);
+        expect(beat.whammyBarPoints!.length).toEqual(2);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(-12);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(44.4);
+        expect(beat.whammyBarPoints![1].value).toEqual(8);
 
         // Bar 9
         beat = score.tracks[0].staves[0].bars[8].voices[0].beats[0];
         expect(beat.whammyBarType).toEqual(WhammyType.Dip);
-        expect(beat.whammyBarPoints.length).toEqual(3);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(8);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(15);
-        expect(beat.whammyBarPoints[1].value).toEqual(12);
-        expect(beat.whammyBarPoints[2].offset).toBeCloseTo(30);
-        expect(beat.whammyBarPoints[2].value).toEqual(0);
+        expect(beat.whammyBarPoints!.length).toEqual(3);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(8);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(15);
+        expect(beat.whammyBarPoints![1].value).toEqual(12);
+        expect(beat.whammyBarPoints![2].offset).toBeCloseTo(30);
+        expect(beat.whammyBarPoints![2].value).toEqual(0);
 
         beat = score.tracks[0].staves[0].bars[8].voices[0].beats[1];
         expect(beat.whammyBarType).toEqual(WhammyType.Dip);
-        expect(beat.whammyBarPoints.length).toEqual(3);
-        expect(beat.whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(beat.whammyBarPoints[0].value).toEqual(0);
-        expect(beat.whammyBarPoints[1].offset).toBeCloseTo(15);
-        expect(beat.whammyBarPoints[1].value).toEqual(-4);
-        expect(beat.whammyBarPoints[2].offset).toBeCloseTo(30);
-        expect(beat.whammyBarPoints[2].value).toEqual(0);
+        expect(beat.whammyBarPoints!.length).toEqual(3);
+        expect(beat.whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(beat.whammyBarPoints![0].value).toEqual(0);
+        expect(beat.whammyBarPoints![1].offset).toBeCloseTo(15);
+        expect(beat.whammyBarPoints![1].value).toEqual(-4);
+        expect(beat.whammyBarPoints![2].offset).toBeCloseTo(30);
+        expect(beat.whammyBarPoints![2].value).toEqual(0);
     });
 
     it('tremolo', async () => {
         const reader = await prepareGp7ImporterWithFile('guitarpro7/tremolo.gp');
         let score: Score = reader.readScore();
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints.length).toEqual(3);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints!.length).toEqual(3);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[1].offset).toBeCloseTo(30);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![1].offset).toBeCloseTo(30);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[2].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[2].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![2].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![2].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints.length).toEqual(2);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints!.length).toEqual(2);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[0].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![0].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[1].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[1].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![1].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![1].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints.length).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints!.length).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[1].offset).toBeCloseTo(30);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![1].offset).toBeCloseTo(30);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[2].offset).toBeCloseTo(30);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[2].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![2].offset).toBeCloseTo(30);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![2].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[3].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[3].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![3].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![3].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints.length).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints!.length).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[0].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![0].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[1].offset).toBeCloseTo(15);
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-12);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![1].offset).toBeCloseTo(15);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-12);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[2].offset).toBeCloseTo(30.6);
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[2].value).toEqual(-12);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![2].offset).toBeCloseTo(30.6);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![2].value).toEqual(-12);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[3].offset).toBeCloseTo(45);
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[3].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![3].offset).toBeCloseTo(45);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![3].value).toEqual(0);
     });
 
     it('slides', async () => {
@@ -765,9 +765,9 @@ describe('Gp7ImporterTest', () => {
     it('fermata', async () => {
         const reader = await prepareGp7ImporterWithFile('guitarpro7/fermata.gp');
         let score: Score = reader.readScore();
-        expect(score.masterBars[0].fermata.size).toEqual(5);
-        expect(score.masterBars[1].fermata.size).toEqual(5);
-        expect(score.masterBars[2].fermata.size).toEqual(5); // Short
+        expect(score.masterBars[0].fermata!.size).toEqual(5);
+        expect(score.masterBars[1].fermata!.size).toEqual(5);
+        expect(score.masterBars[2].fermata!.size).toEqual(5); // Short
         let offsets = [
             0,
             (MidiUtils.QuarterTime * (1 / 2)) | 0,
@@ -778,15 +778,15 @@ describe('Gp7ImporterTest', () => {
         let types: FermataType[] = [FermataType.Short, FermataType.Medium, FermataType.Long];
         for (let i: number = 0; i < 3; i++) {
             let masterBar: MasterBar = score.masterBars[i];
-            expect(masterBar.fermata.size).toEqual(5);
+            expect(masterBar.fermata!.size).toEqual(5);
             for (let offset of offsets) {
-                let fermata = masterBar.fermata.get(offset);
+                let fermata = masterBar.fermata!.get(offset);
                 expect(fermata).toBeTruthy();
                 expect(fermata!.type).toEqual(types[i]);
             }
             let beats: Beat[] = score.tracks[0].staves[0].bars[i].voices[0].beats;
             for (let beat of beats) {
-                let fermata = masterBar.fermata.get(beat.playbackStart);
+                let fermata = masterBar.fermata!.get(beat.playbackStart);
                 let beatFermata = beat.fermata;
                 expect(beatFermata).toBeTruthy();
                 expect(fermata).toBeTruthy();
diff --git a/test/importer/GpImporterTestHelper.ts b/test/importer/GpImporterTestHelper.ts
index dc9aeed2c..965f11fc5 100644
--- a/test/importer/GpImporterTestHelper.ts
+++ b/test/importer/GpImporterTestHelper.ts
@@ -160,74 +160,74 @@ export class GpImporterTestHelper {
     }
 
     public static checkBend(score: Score): void {
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints.length).toEqual(3);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints!.length).toEqual(3);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[0].offset).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![0].offset).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[1].offset).toEqual(15);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[1].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![1].offset).toEqual(15);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![1].value).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[2].offset).toEqual(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[2].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![2].offset).toEqual(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![2].value).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints.length).toEqual(7);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints!.length).toEqual(7);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[0].offset).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![0].offset).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[1].offset).toEqual(10);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[1].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![1].offset).toEqual(10);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![1].value).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[2].offset).toEqual(20);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[2].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![2].offset).toEqual(20);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![2].value).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[3].offset).toEqual(30);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[3].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![3].offset).toEqual(30);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![3].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[4].offset).toEqual(40);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[4].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![4].offset).toEqual(40);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![4].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[5].offset).toEqual(50);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[5].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![5].offset).toEqual(50);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![5].value).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[6].offset).toEqual(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[6].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![6].offset).toEqual(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![6].value).toEqual(4);
     }
 
     public static checkTremolo(score: Score): void {
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints.length).toEqual(3);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints!.length).toEqual(3);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[0].offset).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![0].offset).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[1].offset).toEqual(30);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![1].offset).toEqual(30);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[2].offset).toEqual(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[2].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![2].offset).toEqual(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![2].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints.length).toEqual(3);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints!.length).toEqual(3);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[0].offset).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[0].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![0].offset).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![0].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[1].offset).toEqual(45);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![1].offset).toEqual(45);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[2].offset).toEqual(60);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[2].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![2].offset).toEqual(60);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![2].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints.length).toEqual(3);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints!.length).toEqual(3);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[0].offset).toEqual(0);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![0].offset).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[1].offset).toEqual(45);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![1].offset).toEqual(45);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[2].offset).toEqual(60);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[2].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![2].offset).toEqual(60);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![2].value).toEqual(-4);
     }
 
     public static checkSlides(score: Score): void {
@@ -477,7 +477,7 @@ export class GpImporterTestHelper {
     public static checkChords(score: Score): void {
         let track: Track = score.tracks[0];
         let staff: Staff = track.staves[0];
-        expect(staff.chords.size).toEqual(8);
+        expect(staff.chords!.size).toEqual(8);
 
         GpImporterTestHelper.checkChord(
             GpImporterTestHelper.createChord('C', 1, [0, 1, 0, 2, 3, -1]),
diff --git a/test/importer/GpxImporter.test.ts b/test/importer/GpxImporter.test.ts
index e56fe537e..58b74aa63 100644
--- a/test/importer/GpxImporter.test.ts
+++ b/test/importer/GpxImporter.test.ts
@@ -105,84 +105,84 @@ describe('GpxImporterTest', () => {
     it('bends', async () => {
         const reader = await prepareGpxImporterWithFile('guitarpro6/bends.gpx');
         let score: Score = reader.readScore();
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints.length).toEqual(2);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints!.length).toEqual(2);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[1].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints[1].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![1].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0].bendPoints![1].value).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints.length).toEqual(2);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints!.length).toEqual(2);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[1].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints[1].value).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![1].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[1].notes[0].bendPoints![1].value).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints.length).toEqual(3);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints!.length).toEqual(3);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[1].offset).toBeCloseTo(30);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[1].value).toEqual(12);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![1].offset).toBeCloseTo(30);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![1].value).toEqual(12);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[2].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints[2].value).toEqual(6);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![2].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].notes[0].bendPoints![2].value).toEqual(6);
     });
 
     it('tremolo', async () => {
         const reader = await prepareGpxImporterWithFile('guitarpro6/tremolo.gpx');
         let score: Score = reader.readScore();
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints.length).toEqual(3);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints!.length).toEqual(3);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[1].offset).toBeCloseTo(30);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![1].offset).toBeCloseTo(30);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[2].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints[2].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![2].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].whammyBarPoints![2].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints.length).toEqual(2);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints!.length).toEqual(2);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[0].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![0].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[1].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints[1].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![1].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].whammyBarPoints![1].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints.length).toEqual(3);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints!.length).toEqual(3);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[0].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![0].value).toEqual(0);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[1].offset).toBeCloseTo(30);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![1].offset).toBeCloseTo(30);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[2].offset).toBeCloseTo(60);
-        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints[2].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![2].offset).toBeCloseTo(60);
+        expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].whammyBarPoints![2].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints.length).toEqual(4);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints!.length).toEqual(4);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[0].offset).toBeCloseTo(0);
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[0].value).toEqual(-4);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![0].offset).toBeCloseTo(0);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![0].value).toEqual(-4);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[1].offset).toBeCloseTo(15);
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[1].value).toEqual(-12);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![1].offset).toBeCloseTo(15);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![1].value).toEqual(-12);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[2].offset).toBeCloseTo(30.6);
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[2].value).toEqual(-12);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![2].offset).toBeCloseTo(30.6);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![2].value).toEqual(-12);
 
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[3].offset).toBeCloseTo(45);
-        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints[3].value).toEqual(0);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![3].offset).toBeCloseTo(45);
+        expect(score.tracks[0].staves[0].bars[3].voices[0].beats[0].whammyBarPoints![3].value).toEqual(0);
     });
 
     it('slides', async () => {
diff --git a/test/importer/MusicXmlImporterTestHelper.ts b/test/importer/MusicXmlImporterTestHelper.ts
index 3d6d49913..a700b1526 100644
--- a/test/importer/MusicXmlImporterTestHelper.ts
+++ b/test/importer/MusicXmlImporterTestHelper.ts
@@ -224,7 +224,11 @@ export class MusicXmlImporterTestHelper {
         }
     }
 
-    protected expectBendPointsEqual(expected: BendPoint[], actual: BendPoint[]): void {
+    protected expectBendPointsEqual(expected: BendPoint[] | null, actual: BendPoint[] | null): void {
+        if(expected == null || actual == null) {
+            expect(actual).toEqual(expected)
+            return;
+        }
         expect(actual.length).toEqual(expected.length, 'Mismatch on Count');
         for (let i: number = 0; i < expected.length; i++) {
             expect(actual[i].value).toEqual(actual[i].value);
diff --git a/test/model/ComparisonHelpers.ts b/test/model/ComparisonHelpers.ts
index 00e150099..cb3ce45e2 100644
--- a/test/model/ComparisonHelpers.ts
+++ b/test/model/ComparisonHelpers.ts
@@ -1,6 +1,13 @@
-
+/**
+ * @partial
+ */
 export class ComparisonHelpers {
-    public static expectJsonEqual(expected: unknown, actual: unknown, path: string, ignoreKeys: string[] | null): boolean {
+    public static expectJsonEqual(
+        expected: unknown,
+        actual: unknown,
+        path: string,
+        ignoreKeys: string[] | null
+    ): boolean {
         const expectedType = typeof expected;
         const actualType = typeof actual;
 
@@ -40,7 +47,14 @@ export class ComparisonHelpers {
                             result = false;
                         } else {
                             for (let i = 0; i < actual.length; i++) {
-                                if(!ComparisonHelpers.expectJsonEqual(expected[i], actual[i], `${path}[${i}]`, ignoreKeys)) {
+                                if (
+                                    !ComparisonHelpers.expectJsonEqual(
+                                        expected[i],
+                                        actual[i],
+                                        `${path}[${i}]`,
+                                        ignoreKeys
+                                    )
+                                ) {
                                     result = false;
                                 }
                             }
@@ -53,46 +67,54 @@ export class ComparisonHelpers {
                             const expectedMap = expected as Map<string, unknown>;
                             const actualMap = actual as Map<string, unknown>;
 
-                            const expectedKeys = Array.from(expectedMap.keys());
-                            const actualKeys = Array.from(actualMap.keys());
+                            const ignoredKeyLookup: Set<string> = new Set<string>([
+                                'id',
+                                'hammerpulloriginnoteid',
+                                'hammerpulldestinationnoteid',
+                                'tieoriginnoteid',
+                                'tiedestinationnoteid',
+                                'sluroriginnoteid',
+                                'slurdestinationnoteid'
+                            ]);
+                            if (ignoreKeys) {
+                                for (const k of ignoreKeys) {
+                                    ignoredKeyLookup.add(k);
+                                }
+                            }
+                            const expectedKeys = Array.from(expectedMap.keys()).filter(k => !ignoredKeyLookup.has(k));
+                            const actualKeys = Array.from(actualMap.keys()).filter(k => !ignoredKeyLookup.has(k));
                             expectedKeys.sort();
                             actualKeys.sort();
 
                             const actualKeyList = actualKeys.join(',');
                             const expectedKeyList = expectedKeys.join(',');
                             if (actualKeyList !== expectedKeyList) {
-                                fail(`Object Keys mismatch on hierarchy: ${path}, '${actualKeyList}' != '${expectedKeyList}'`);
+                                fail(
+                                    `Object Keys mismatch on hierarchy: ${path}, '${actualKeyList}' != '${expectedKeyList}'`
+                                );
                                 result = false;
                             } else {
                                 for (const key of actualKeys) {
-                                    switch (key) {
-                                        // some ignored keys
-                                        case 'id':
-                                        case 'hammerPullOriginNoteId':
-                                        case 'hammerPullDestinationNoteId':
-                                        case 'tieOriginNoteId':
-                                        case 'tieDestinationNoteId':
-                                            break;
-                                        default:
-                                            if (!ignoreKeys || ignoreKeys.indexOf(key) === -1) {
-                                                if(!ComparisonHelpers.expectJsonEqual(expectedMap.get(key), actualMap.get(key), `${path}.${key}`, ignoreKeys)) {
-                                                    result = false;
-                                                }
-                                            }
-                                            break;
+                                    if (
+                                        !ComparisonHelpers.expectJsonEqual(
+                                            expectedMap.get(key),
+                                            actualMap.get(key),
+                                            `${path}.${key}`,
+                                            ignoreKeys
+                                        )
+                                    ) {
+                                        result = false;
                                     }
                                 }
-
                             }
                         }
-                    } else {
-                        fail('Need Map serialization for comparing json objects');
+                    } else if (!ComparisonHelpers.compareObjects(expected, actual, path, ignoreKeys)) {
                         result = false;
                     }
                 }
                 break;
             case 'string':
-                if ((actual as string) != (expected as string)) {
+                if ((actual as string) !== (expected as string)) {
                     fail(`String mismatch on hierarchy: ${path}, '${actual}' != '${expected}'`);
                     result = false;
                 }
@@ -108,5 +130,17 @@ export class ComparisonHelpers {
         return result;
     }
 
-
-}
\ No newline at end of file
+    /**
+     * @target web
+     * @partial
+     */
+    public static compareObjects(
+        expected: unknown,
+        actual: unknown,
+        path: string,
+        ignoreKeys: string[] | null
+    ): boolean {
+        fail(`Cannot compare unknown object types on path ${path}`);
+        return false;
+    }
+}
diff --git a/tsconfig.base.json b/tsconfig.base.json
index f7f93b617..3faf0ed5f 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -18,6 +18,7 @@
     "noImplicitThis": true,
     "noImplicitReturns": true,
     "noUnusedLocals": true,
+    "noImplicitOverride": true,
     "sourceMap": true,
     "declaration": true,
     "incremental": true,