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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src.compiler/csharp/CSharpAst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,8 @@ export interface NonNullExpression extends Node {

export interface TypeOfExpression extends Node {
nodeType: SyntaxKind.TypeOfExpression;
expression: Expression;
expression?: Expression;
type?: TypeNode;
}

export interface NullSafeExpression extends Node {
Expand Down
7 changes: 5 additions & 2 deletions src.compiler/csharp/CSharpAstPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -707,11 +707,14 @@ export default class CSharpAstPrinter extends AstPrinterBase {

protected writeTypeOfExpression(expr: cs.TypeOfExpression) {
this.write('typeof');

this.write('(');
if (expr.expression) {
this.write('(');
this.writeExpression(expr.expression);
this.write(')');
} else if (expr.type) {
this.writeType(expr.type);
}
this.write(')');
}

protected writePrefixUnaryExpression(expr: cs.PrefixUnaryExpression) {
Expand Down
70 changes: 54 additions & 16 deletions src.compiler/csharp/CSharpAstTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3546,8 +3546,37 @@ export default class CSharpAstTransformer {
}

protected visitElementAccessExpression(parent: cs.Node, expression: ts.ElementAccessExpression) {
// Enum[value] => value.ToString()
if (this.isEnumToString(expression)) {
// Enum[enumValue] => value.toString()
// Enum[string] => TypeHelper.parseEnum<Type>(value, Type)
if (this.isEnumFromOrToString(expression)) {
const elementType = this._context.typeChecker.getTypeAtLocation(expression.argumentExpression);

if (this._context.isEnum(elementType)) {
const callExpr = {
parent: parent,
arguments: [],
expression: {} as cs.Expression,
nodeType: cs.SyntaxKind.InvocationExpression,
tsNode: expression
} as cs.InvocationExpression;

const memberAccess = {
expression: {} as cs.Expression,
member: this._context.toPascalCase('toString'),
parent: callExpr,
tsNode: expression,
nodeType: cs.SyntaxKind.MemberAccessExpression
} as cs.MemberAccessExpression;
callExpr.expression = memberAccess;

memberAccess.expression = this.visitExpression(memberAccess, expression.argumentExpression)!;
if (!memberAccess.expression) {
return null;
}

return callExpr;
}

const callExpr = {
parent: parent,
arguments: [],
Expand All @@ -3556,23 +3585,32 @@ export default class CSharpAstTransformer {
tsNode: expression
} as cs.InvocationExpression;

const memberAccess = {
expression: {} as cs.Expression,
member: this._context.toPascalCase('toString'),
parent: callExpr,
tsNode: expression,
nodeType: cs.SyntaxKind.MemberAccessExpression
} as cs.MemberAccessExpression;
callExpr.expression = memberAccess;
callExpr.expression = this.makeMemberAccess(
callExpr,
this._context.makeTypeName('alphaTab.core.TypeHelper'),
this._context.toMethodName('parseEnum')
);

memberAccess.expression = this.visitExpression(memberAccess, expression.argumentExpression)!;
if (!memberAccess.expression) {
return null;
}
const enumType = this._context.typeChecker.getTypeAtLocation(expression.expression);
callExpr.typeArguments = [
this.createUnresolvedTypeNode(callExpr, expression.argumentExpression, enumType, enumType.symbol)
];

const typeOf: cs.TypeOfExpression = {
nodeType: cs.SyntaxKind.TypeOfExpression,
parent: callExpr
};
typeOf.type = this.createUnresolvedTypeNode(
typeOf,
expression.argumentExpression,
enumType,
enumType.symbol
);

callExpr.arguments = [this.visitExpression(callExpr, expression.argumentExpression)!, typeOf];

return callExpr;
}

const elementAccess = {
expression: {} as cs.Expression,
argumentExpression: {} as cs.Expression,
Expand Down Expand Up @@ -3656,7 +3694,7 @@ export default class CSharpAstTransformer {
return this.wrapToSmartCast(parent, elementAccess, expression, forceCast);
}

protected isEnumToString(expression: ts.ElementAccessExpression): boolean {
protected isEnumFromOrToString(expression: ts.ElementAccessExpression): boolean {
const enumType = this._context.typeChecker.getTypeAtLocation(expression.expression);
return !!(enumType?.symbol && enumType.symbol.flags & ts.SymbolFlags.RegularEnum);
}
Expand Down
11 changes: 11 additions & 0 deletions src.compiler/csharp/CSharpEmitterContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,17 @@ export default class CSharpEmitterContext {
return this.applyNullable(csType, typeNode);
}

if (typeNode && ts.isTypeOperatorNode(typeNode)) {
if (typeNode.operator === ts.SyntaxKind.KeyOfKeyword) {
return {
nodeType: cs.SyntaxKind.PrimitiveTypeNode,
type: cs.PrimitiveType.String,
isNullable: false
} as cs.PrimitiveTypeNode;
}
return null;
}

csType = this.resolvePrimitiveType(node, tsType);
if (csType) {
return this.applyNullable(csType, typeNode);
Expand Down
7 changes: 6 additions & 1 deletion src.compiler/kotlin/KotlinAstPrinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,8 +968,13 @@ export default class KotlinAstPrinter extends AstPrinterBase {
protected writeTypeOfExpression(expr: cs.TypeOfExpression) {
if (expr.expression) {
this.writeExpression(expr.expression);
this.write('::class');
}
if (expr.type) {
this.writeType(expr.type);
}

this.write('::class');

}

protected writePrefixUnaryExpression(expr: cs.PrefixUnaryExpression) {
Expand Down
5 changes: 5 additions & 0 deletions src.csharp/AlphaTab/Core/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public static IList<T> CreateList<T>(params T[] values)
return new List<T>(values);
}

public static T ParseEnum<T>(string s, Type _) where T : struct
{
return Enum.TryParse(s, true, out T value) ? value : default;
}

public static void Add<T>(this IList<T> list, IList<T> newItems)
{
if (list is List<T> l)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package alphaTab.core

import alphaTab.AlphaTabError
import alphaTab.AlphaTabErrorType
import alphaTab.core.ecmaScript.RegExp
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.reflect.KClass

typealias Disposable = AutoCloseable

Expand All @@ -16,6 +19,24 @@ internal class TypeHelper {
return RegExp(pattern, flags)
}

@ExperimentalContracts
@ExperimentalUnsignedTypes
public inline fun <reified T : Enum<T>> parseEnum(value: String, @Suppress("UNUSED_PARAMETER") type: KClass<T>): T {
return parseEnum(value, enumValues<T>())
}

@ExperimentalContracts
@ExperimentalUnsignedTypes
public fun <T : Enum<T>> parseEnum(value: String, values: Array<T>): T {
val valueLower = value.lowercase()
for (e in values) {
if (valueLower.equals(e.name, true)) {
return e
}
}
throw AlphaTabError(AlphaTabErrorType.General, "Could not parse enum value '$value'")
}

@ExperimentalContracts
public fun isTruthy(s: String?): Boolean {
contract { returns(true) implies (s != null) }
Expand Down
53 changes: 29 additions & 24 deletions src/importer/AlphaTexImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2109,30 +2109,35 @@ export class AlphaTexImporter extends ScoreImporter {
return true;
} else if (syData === 'dy') {
this._sy = this.newSy();
switch ((this._syData as string).toLowerCase()) {
case 'ppp':
beat.dynamics = DynamicValue.PPP;
break;
case 'pp':
beat.dynamics = DynamicValue.PP;
break;
case 'p':
beat.dynamics = DynamicValue.P;
break;
case 'mp':
beat.dynamics = DynamicValue.MP;
break;
case 'mf':
beat.dynamics = DynamicValue.MF;
break;
case 'f':
beat.dynamics = DynamicValue.F;
break;
case 'ff':
beat.dynamics = DynamicValue.FF;
break;
case 'fff':
beat.dynamics = DynamicValue.FFF;
const dynamicString = (this._syData as string).toUpperCase() as keyof typeof DynamicValue;
switch (dynamicString) {
case 'PPP':
case 'PP':
case 'P':
case 'MP':
case 'MF':
case 'F':
case 'FF':
case 'FFF':
case 'PPPP':
case 'PPPPP':
case 'PPPPPP':
case 'FFFF':
case 'FFFFF':
case 'FFFFFF':
case 'SF':
case 'SFP':
case 'SFPP':
case 'FP':
case 'RF':
case 'RFZ':
case 'SFZ':
case 'SFFZ':
case 'FZ':
case 'N':
case 'PF':
case 'SFZP':
beat.dynamics = DynamicValue[dynamicString];
break;
}
this._currentDynamics = beat.dynamics;
Expand Down
67 changes: 30 additions & 37 deletions src/importer/MusicXmlImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2196,43 +2196,36 @@ export class MusicXmlImporter extends ScoreImporter {

private parseDynamics(element: XmlNode) {
for (const c of element.childElements()) {
switch (c.localName) {
case 'p':
return DynamicValue.P;
case 'pp':
return DynamicValue.PP;
case 'ppp':
return DynamicValue.PPP;
// case 'pppp': not supported
// case 'ppppp': not supported
// case 'pppppp': not supported

case 'f':
return DynamicValue.F;
case 'ff':
return DynamicValue.FF;
case 'fff':
return DynamicValue.FFF;
// case 'ffff': not supported
// case 'fffff': not supported
// case 'ffffff': not supported

case 'mp':
return DynamicValue.MP;
case 'mf':
return DynamicValue.MF;
// case 'sf': not supported
// case 'sfp': not supported
// case 'sfpp': not supported
// case 'fp': not supported
// case 'rf': not supported
// case 'rfz': not supported
// case 'sfz': not supported
// case 'sffz': not supported
// case 'fz': not supported
// case 'n': not supported
// case 'pf': not supported
// case 'sfzp': not supported
// we are having the same enum names as MusicXML uses as tagnames
const dynamicString = c.localName!.toUpperCase() as keyof typeof DynamicValue;
switch (dynamicString) {
case 'PPP':
case 'PP':
case 'P':
case 'MP':
case 'MF':
case 'F':
case 'FF':
case 'FFF':
case 'PPPP':
case 'PPPPP':
case 'PPPPPP':
case 'FFFF':
case 'FFFFF':
case 'FFFFFF':
case 'SF':
case 'SFP':
case 'SFPP':
case 'FP':
case 'RF':
case 'RFZ':
case 'SFZ':
case 'SFFZ':
case 'FZ':
case 'N':
case 'PF':
case 'SFZP':
return DynamicValue[dynamicString];
// case 'other-dynamics': not supported
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/midi/MidiFileGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ export class MidiFileGenerator {
beatStart,
deadSlapDuration,
t,
MidiUtils.dynamicToVelocity(DynamicValue.F as number),
MidiUtils.dynamicToVelocity(DynamicValue.F),
staff.track.playbackInfo.primaryChannel
);
}
Expand Down Expand Up @@ -923,26 +923,26 @@ export class MidiFileGenerator {
}

private static getNoteVelocity(note: Note): number {
let dynamicValue: number = note.dynamics as number;
let adjustment: number = 0;
// more silent on hammer destination
if (!note.beat.voice.bar.staff.isPercussion && note.hammerPullOrigin) {
dynamicValue--;
adjustment--;
}
// more silent on ghost notes
if (note.isGhost) {
dynamicValue--;
adjustment--;
}
// louder on accent
switch (note.accentuated) {
case AccentuationType.Normal:
dynamicValue++;
adjustment++;
break;
case AccentuationType.Heavy:
dynamicValue += 2;
adjustment += 2;
break;
}

return MidiUtils.dynamicToVelocity(dynamicValue);
return MidiUtils.dynamicToVelocity(note.dynamics, adjustment);
}

private generateFade(beat: Beat, beatStart: number, beatDuration: number): void {
Expand Down
Loading