takomusic 1.3.10 → 1.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/generators.test.js +32 -8
- package/dist/__tests__/generators.test.js.map +1 -1
- package/dist/checker/checker.d.ts.map +1 -1
- package/dist/checker/checker.js +23 -1
- package/dist/checker/checker.js.map +1 -1
- package/dist/cli/commands/build.js +1 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/formatter/formatter.d.ts.map +1 -1
- package/dist/formatter/formatter.js +6 -1
- package/dist/formatter/formatter.js.map +1 -1
- package/dist/generators/musicxml.d.ts +1 -1
- package/dist/generators/musicxml.d.ts.map +1 -1
- package/dist/generators/musicxml.js +18 -1
- package/dist/generators/musicxml.js.map +1 -1
- package/dist/interpreter/interpreter.d.ts.map +1 -1
- package/dist/interpreter/interpreter.js +76 -5
- package/dist/interpreter/interpreter.js.map +1 -1
- package/dist/interpreter/runtime.d.ts +5 -3
- package/dist/interpreter/runtime.d.ts.map +1 -1
- package/dist/interpreter/runtime.js +3 -0
- package/dist/interpreter/runtime.js.map +1 -1
- package/dist/lexer/lexer.d.ts.map +1 -1
- package/dist/lexer/lexer.js +17 -1
- package/dist/lexer/lexer.js.map +1 -1
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +35 -5
- package/dist/parser/parser.js.map +1 -1
- package/dist/types/ast.d.ts +4 -3
- package/dist/types/ast.d.ts.map +1 -1
- package/dist/utils/kanji.d.ts +18 -0
- package/dist/utils/kanji.d.ts.map +1 -0
- package/dist/utils/kanji.js +77 -0
- package/dist/utils/kanji.js.map +1 -0
- package/package.json +3 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Interpreter for MFS language
|
|
2
|
-
import { makeInt, makeFloat, makeString, makeBool, makePitch, makeDur, makeTime, makeArray, makeObject, makeFunction, makeNull, toNumber, isTruthy, toString, deepClone, } from './runtime.js';
|
|
2
|
+
import { makeInt, makeFloat, makeString, makeBool, makePitch, makeDur, makeDurTicks, makeTime, makeArray, makeObject, makeFunction, makeNull, toNumber, isTruthy, toString, deepClone, } from './runtime.js';
|
|
3
3
|
import { Scope } from './scope.js';
|
|
4
4
|
import { createError, MFError } from '../errors.js';
|
|
5
5
|
import * as coreBuiltins from './builtins/core.js';
|
|
@@ -463,11 +463,21 @@ export class Interpreter {
|
|
|
463
463
|
case 'PitchLiteral':
|
|
464
464
|
return makePitch(expr.midi);
|
|
465
465
|
case 'DurLiteral':
|
|
466
|
-
//
|
|
466
|
+
// Handle tick-based duration
|
|
467
|
+
if (expr.ticks !== undefined) {
|
|
468
|
+
if (expr.ticks <= 0) {
|
|
469
|
+
throw new MFError('E101', `Invalid duration: ${expr.ticks}t (must be positive)`, expr.position, this.filePath);
|
|
470
|
+
}
|
|
471
|
+
return makeDurTicks(expr.ticks);
|
|
472
|
+
}
|
|
473
|
+
// Handle fraction/note-based duration
|
|
474
|
+
if (expr.numerator === undefined || expr.denominator === undefined) {
|
|
475
|
+
throw new MFError('E101', 'Invalid duration literal', expr.position, this.filePath);
|
|
476
|
+
}
|
|
467
477
|
if (expr.numerator <= 0 || expr.denominator <= 0) {
|
|
468
478
|
throw new MFError('E101', `Invalid duration: ${expr.numerator}/${expr.denominator} (must be positive)`, expr.position, this.filePath);
|
|
469
479
|
}
|
|
470
|
-
return makeDur(expr.numerator, expr.denominator, expr.dots);
|
|
480
|
+
return makeDur(expr.numerator, expr.denominator, expr.dots ?? 0);
|
|
471
481
|
case 'TimeLiteral':
|
|
472
482
|
return makeTime(expr.bar, expr.beat, expr.sub);
|
|
473
483
|
case 'ArrayLiteral': {
|
|
@@ -660,6 +670,14 @@ export class Interpreter {
|
|
|
660
670
|
}
|
|
661
671
|
// Dur + Dur
|
|
662
672
|
if (left.type === 'dur' && right.type === 'dur') {
|
|
673
|
+
// Both must be fraction-based for arithmetic
|
|
674
|
+
if (left.ticks !== undefined || right.ticks !== undefined) {
|
|
675
|
+
throw createError('E400', 'Cannot perform arithmetic on tick-based durations', leftExpr.position, this.filePath);
|
|
676
|
+
}
|
|
677
|
+
if (left.numerator === undefined || left.denominator === undefined ||
|
|
678
|
+
right.numerator === undefined || right.denominator === undefined) {
|
|
679
|
+
throw createError('E400', 'Invalid duration for arithmetic', leftExpr.position, this.filePath);
|
|
680
|
+
}
|
|
663
681
|
const num = left.numerator * right.denominator + right.numerator * left.denominator;
|
|
664
682
|
const den = left.denominator * right.denominator;
|
|
665
683
|
const gcd = this.gcd(num, den);
|
|
@@ -689,6 +707,13 @@ export class Interpreter {
|
|
|
689
707
|
if (op === '*') {
|
|
690
708
|
// Dur * Int
|
|
691
709
|
if (left.type === 'dur' && right.type === 'int') {
|
|
710
|
+
// Handle tick-based duration
|
|
711
|
+
if (left.ticks !== undefined) {
|
|
712
|
+
return makeDurTicks(left.ticks * right.value);
|
|
713
|
+
}
|
|
714
|
+
if (left.numerator === undefined || left.denominator === undefined) {
|
|
715
|
+
throw createError('E400', 'Invalid duration for arithmetic', leftExpr.position, this.filePath);
|
|
716
|
+
}
|
|
692
717
|
const num = left.numerator * right.value;
|
|
693
718
|
const gcd = this.gcd(num, left.denominator);
|
|
694
719
|
return makeDur(num / gcd, left.denominator / gcd);
|
|
@@ -707,6 +732,13 @@ export class Interpreter {
|
|
|
707
732
|
if (right.value === 0) {
|
|
708
733
|
throw createError('E400', 'Division by zero', leftExpr.position, this.filePath);
|
|
709
734
|
}
|
|
735
|
+
// Handle tick-based duration
|
|
736
|
+
if (left.ticks !== undefined) {
|
|
737
|
+
return makeDurTicks(Math.round(left.ticks / right.value));
|
|
738
|
+
}
|
|
739
|
+
if (left.numerator === undefined || left.denominator === undefined) {
|
|
740
|
+
throw createError('E400', 'Invalid duration for arithmetic', leftExpr.position, this.filePath);
|
|
741
|
+
}
|
|
710
742
|
const den = left.denominator * right.value;
|
|
711
743
|
const gcd = this.gcd(left.numerator, den);
|
|
712
744
|
if (gcd === 0) {
|
|
@@ -7714,10 +7746,20 @@ export class Interpreter {
|
|
|
7714
7746
|
let fromNote = { numerator: 1, denominator: 4 };
|
|
7715
7747
|
let toNote = { numerator: 1, denominator: 4 };
|
|
7716
7748
|
if (fromNoteArg.type === 'dur') {
|
|
7717
|
-
|
|
7749
|
+
if (fromNoteArg.ticks !== undefined) {
|
|
7750
|
+
throw new MFError('TYPE', 'metricMod() requires fraction-based durations, not tick-based', position, this.filePath);
|
|
7751
|
+
}
|
|
7752
|
+
if (fromNoteArg.numerator !== undefined && fromNoteArg.denominator !== undefined) {
|
|
7753
|
+
fromNote = { numerator: fromNoteArg.numerator, denominator: fromNoteArg.denominator };
|
|
7754
|
+
}
|
|
7718
7755
|
}
|
|
7719
7756
|
if (toNoteArg.type === 'dur') {
|
|
7720
|
-
|
|
7757
|
+
if (toNoteArg.ticks !== undefined) {
|
|
7758
|
+
throw new MFError('TYPE', 'metricMod() requires fraction-based durations, not tick-based', position, this.filePath);
|
|
7759
|
+
}
|
|
7760
|
+
if (toNoteArg.numerator !== undefined && toNoteArg.denominator !== undefined) {
|
|
7761
|
+
toNote = { numerator: toNoteArg.numerator, denominator: toNoteArg.denominator };
|
|
7762
|
+
}
|
|
7721
7763
|
}
|
|
7722
7764
|
if (!track.metricModulations)
|
|
7723
7765
|
track.metricModulations = [];
|
|
@@ -8493,11 +8535,40 @@ export class Interpreter {
|
|
|
8493
8535
|
}
|
|
8494
8536
|
}
|
|
8495
8537
|
durToTicks(dur, position) {
|
|
8538
|
+
// Handle tick-based duration directly
|
|
8539
|
+
if (dur.ticks !== undefined) {
|
|
8540
|
+
let ticks = dur.ticks;
|
|
8541
|
+
// Apply nested tuplet ratios
|
|
8542
|
+
if (this.currentTrack?.tupletStack && this.currentTrack.tupletStack.length > 0) {
|
|
8543
|
+
for (const tuplet of this.currentTrack.tupletStack) {
|
|
8544
|
+
ticks = (ticks * tuplet.normal) / tuplet.actual;
|
|
8545
|
+
}
|
|
8546
|
+
ticks = Math.round(ticks);
|
|
8547
|
+
}
|
|
8548
|
+
if (ticks < 1) {
|
|
8549
|
+
throw createError('E101', `Duration too small after tuplet application`, position, this.filePath);
|
|
8550
|
+
}
|
|
8551
|
+
return ticks;
|
|
8552
|
+
}
|
|
8553
|
+
// Handle fraction-based duration
|
|
8554
|
+
if (dur.numerator === undefined || dur.denominator === undefined) {
|
|
8555
|
+
throw createError('E101', 'Invalid duration value', position, this.filePath);
|
|
8556
|
+
}
|
|
8496
8557
|
if (this.ir.ppq === 0) {
|
|
8497
8558
|
throw createError('E001', 'ppq not set', position, this.filePath);
|
|
8498
8559
|
}
|
|
8499
8560
|
// ticks = ppq * 4 * n / d
|
|
8500
8561
|
let ticks = (this.ir.ppq * 4 * dur.numerator) / dur.denominator;
|
|
8562
|
+
// Apply dotted note multiplier
|
|
8563
|
+
if (dur.dots && dur.dots > 0) {
|
|
8564
|
+
let multiplier = 1;
|
|
8565
|
+
let addition = 0.5;
|
|
8566
|
+
for (let i = 0; i < dur.dots; i++) {
|
|
8567
|
+
multiplier += addition;
|
|
8568
|
+
addition /= 2;
|
|
8569
|
+
}
|
|
8570
|
+
ticks *= multiplier;
|
|
8571
|
+
}
|
|
8501
8572
|
// Apply nested tuplet ratios (multiply all normal/actual ratios)
|
|
8502
8573
|
if (this.currentTrack?.tupletStack && this.currentTrack.tupletStack.length > 0) {
|
|
8503
8574
|
for (const tuplet of this.currentTrack.tupletStack) {
|