Skip to content

Commit

Permalink
Compress interval JSON representation
Browse files Browse the repository at this point in the history
ref #322
  • Loading branch information
frostburn committed May 17, 2024
1 parent de7fa09 commit 6799f85
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 56 deletions.
2 changes: 1 addition & 1 deletion src/__tests__/interval.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ describe('Interchange format', () => {
});

const SERIALIZED =
'["hello",{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":2,"d":1},{"n":1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"linear","steps":0,"label":"","node":{"type":"IntegerLiteral","value":"12"},"trackingIds":[]},12,{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":1,"d":1},{"n":1,"d":1},{"n":-1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"linear","steps":0,"label":"","node":{"type":"DecimalLiteral","sign":"","whole":"1","fractional":"2","exponent":null,"flavor":"e"},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":0,"d":1},{"n":-1,"d":1},{"n":1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"linear","steps":0,"label":"","node":{"type":"FractionLiteral","numerator":"5","denominator":"3"},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":1,"d":3},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"linear","steps":0,"label":"","node":{"type":"RadicalLiteral","argument":{"n":2,"d":1},"exponent":{"n":1,"d":3}},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":46797,"d":80000}],"residual":{"n":1,"d":1}},"domain":"logarithmic","steps":0,"label":"","node":{"type":"CentsLiteral","sign":"","whole":"701","fractional":"955","exponent":null,"real":false},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":-4,"d":1},{"n":4,"d":1},{"n":-1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"logarithmic","steps":0,"label":"","node":{"type":"SquareSuperparticular","start":"9","end":null},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[],"residual":{"n":1,"d":1}},"domain":"logarithmic","steps":3,"label":"","node":{"type":"StepLiteral","count":3},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":0,"d":1},{"n":6,"d":13},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"logarithmic","steps":0,"label":"","node":{"type":"NedjiLiteral","numerator":6,"denominator":13,"equaveNumerator":3,"equaveDenominator":null},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":-2,"d":1},{"n":0,"d":1},{"n":1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"logarithmic","steps":0,"label":"","node":{"ups":0,"lifts":0,"type":"FJS","pythagorean":{"type":"Pythagorean","quality":{"fraction":"","quality":"M"},"degree":{"negative":false,"base":3,"octaves":0,"imperfect":true}},"superscripts":[[5,""]],"subscripts":[]},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":1,"d":1},{"n":-2,"d":1},{"n":0,"d":1},{"n":1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"logarithmic","steps":0,"label":"","node":{"ups":0,"lifts":0,"type":"AbsoluteFJS","pitch":{"type":"AbsolutePitch","nominal":"A","accidentals":[{"fraction":"","accidental":"b"}],"octave":4},"superscripts":[[7,""]],"subscripts":[]},"trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":-1,"d":1},"primeExponents":[{"n":0,"d":1},{"n":1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":37,"d":1}},"domain":"linear","steps":0,"label":"","trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":1,"d":1},"primeExponents":[{"n":-3,"d":1},{"n":1,"d":1},{"n":-3,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},"domain":"linear","steps":0,"label":"","trackingIds":[]},{"type":"Interval","value":{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":-3,"d":1},{"n":1,"d":1},{"n":1,"d":1}],"residual":{"n":1,"d":1}},"domain":"logarithmic","steps":0,"label":"","node":{"ups":0,"lifts":0,"type":"MonzoLiteral","components":[{"sign":"-","left":3,"separator":"","right":"","exponent":null},{"sign":"","left":1,"separator":"","right":"","exponent":null},{"sign":"","left":1,"separator":"","right":"","exponent":null}],"basis":[]},"trackingIds":[]}]';
'["hello",{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[2,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":0,"s":0,"l":"","n":{"type":"i","v":"12"},"t":[]},12,{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[1,1,1,1,-1,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":0,"s":0,"l":"","n":{"type":"DecimalLiteral","sign":"","whole":"1","fractional":"2","exponent":null,"flavor":"e"},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[0,1,-1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":0,"s":0,"l":"","n":{"type":"f","n":"5","d":"3"},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[1,3,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":0,"s":0,"l":"","n":{"type":"RadicalLiteral","argument":{"n":2,"d":1},"exponent":{"n":1,"d":3}},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[46797,80000],"r":{"n":1,"d":1}},"d":1,"s":0,"l":"","n":{"type":"c","s":"","w":"701","f":"955","e":null,"r":false},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[-4,1,4,1,-1,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":1,"s":0,"l":"","n":{"type":"SquareSuperparticular","start":"9","end":null},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[],"r":{"n":1,"d":1}},"d":1,"s":3,"l":"","n":{"type":"StepLiteral","count":3},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[0,1,6,13,0,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":1,"s":0,"l":"","n":{"type":"n","n":6,"d":13,"p":3,"q":null},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[-2,1,0,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":1,"s":0,"l":"","n":{"ups":0,"lifts":0,"type":"FJS","pythagorean":{"type":"Pythagorean","quality":{"fraction":"","quality":"M"},"degree":{"negative":false,"base":3,"octaves":0,"imperfect":true}},"superscripts":[[5,""]],"subscripts":[]},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[1,1,-2,1,0,1,1,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":1,"s":0,"l":"","n":{"ups":0,"lifts":0,"type":"AbsoluteFJS","pitch":{"type":"AbsolutePitch","nominal":"A","accidentals":[{"fraction":"","accidental":"b"}],"octave":4},"superscripts":[[7,""]],"subscripts":[]},"t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":-1,"d":1},"p":[0,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":37,"d":1}},"d":0,"s":0,"l":"","t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":1,"d":1},"p":[-3,1,1,1,-3,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},"d":0,"s":0,"l":"","t":[]},{"type":"Interval","v":{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[-3,1,1,1,1,1],"r":{"n":1,"d":1}},"d":1,"s":0,"l":"","n":{"ups":0,"lifts":0,"type":"MonzoLiteral","components":[{"sign":"-","left":3,"separator":"","right":"","exponent":null},{"sign":"","left":1,"separator":"","right":"","exponent":null},{"sign":"","left":1,"separator":"","right":"","exponent":null}],"basis":[]},"t":[]}]';

describe('Interval JSON serialization', () => {
it('can be serialized alongside other data', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/monzo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,13 +466,13 @@ describe('JSON serialization', () => {
];
const serialized = JSON.stringify(data);
expect(serialized).toBe(
'["Hello, world!",{"n":10,"d":7},{"type":"TimeReal","timeExponent":-1,"value":777},3.5,{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":-4,"d":1},{"n":4,"d":1},{"n":-1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},null,{"type":"TimeReal","timeExponent":0,"value":"nan"},{"type":"TimeReal","timeExponent":1,"value":"inf"}]'
'["Hello, world!",{"n":10,"d":7},{"type":"TimeReal","t":-1,"v":777},3.5,{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[-4,1,4,1,-1,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},null,{"type":"TimeReal","t":0,"v":"nan"},{"type":"TimeReal","t":1,"v":"inf"}]'
);
});

it('can deserialize an array of primitives, fractions and monzos', () => {
const serialized =
'["Hello, world!",{"n":10,"d":7},{"type":"TimeReal","timeExponent":-1,"value":777},3.5,{"type":"TimeMonzo","timeExponent":{"n":0,"d":1},"primeExponents":[{"n":-4,"d":1},{"n":4,"d":1},{"n":-1,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1},{"n":0,"d":1}],"residual":{"n":1,"d":1}},null,{"type":"TimeReal","timeExponent":0,"value":"nan"},{"type":"TimeReal","timeExponent":1,"value":"inf"}]';
'["Hello, world!",{"n":10,"d":7},{"type":"TimeReal","t":-1,"v":777},3.5,{"type":"TimeMonzo","t":{"n":0,"d":1},"p":[-4,1,4,1,-1,1,0,1,0,1,0,1,0,1,0,1,0,1],"r":{"n":1,"d":1}},null,{"type":"TimeReal","t":0,"v":"nan"},{"type":"TimeReal","t":1,"v":"inf"}]';
function reviver(key: string, value: any) {
return TimeMonzo.reviver(
key,
Expand Down
82 changes: 58 additions & 24 deletions src/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1156,35 +1156,48 @@ export function literalToJSON(literal?: IntervalLiteral): any {
}
const type = literal.type;
switch (literal.type) {
// Compress most common types
case 'IntegerLiteral':
return {type, value: literal.value.toString()};
case 'DecimalLiteral':
return {...literal, whole: literal.whole.toString()};
return {type: 'i', v: literal.value.toString()};
case 'FractionLiteral':
return {
type,
numerator: literal.numerator.toString(),
denominator: literal.denominator.toString(),
type: 'f',
n: literal.numerator.toString(),
d: literal.denominator.toString(),
};
case 'CentsLiteral':
return {
type: 'c',
s: literal.sign,
w: literal.whole.toString(),
f: literal.fractional,
e: literal.exponent,
r: literal.real,
};
case 'NedjiLiteral':
return {
type: 'n',
n: literal.numerator,
d: literal.denominator,
p: literal.equaveNumerator,
q: literal.equaveDenominator,
};
// Keep the rest more human-readable
case 'DecimalLiteral':
return {...literal, whole: literal.whole.toString()};
case 'RadicalLiteral':
return {
type,
argument: literal.argument.toJSON(),
exponent: literal.exponent.toJSON(),
};
case 'CentsLiteral':
return {
...literal,
whole: literal.whole.toString(),
};
case 'SquareSuperparticular':
return {
type,
start: literal.start.toString(),
end: literal.end && literal.end.toString(),
};
case 'StepLiteral':
case 'NedjiLiteral':
case 'CentLiteral':
case 'ReciprocalCentLiteral':
case 'NotANumberLiteral':
Expand All @@ -1209,34 +1222,50 @@ export function literalFromJSON(object: any): IntervalLiteral | undefined {
if (object === undefined) {
return undefined;
}
const type: IntervalLiteral['type'] = object.type;
const type: IntervalLiteral['type'] | 'i' | 'f' | 'c' | 'n' = object.type;
switch (type) {
case 'IntegerLiteral':
return {type, value: BigInt(object.value)};
case 'DecimalLiteral':
return {...object, whole: BigInt(object.whole)};
case 'FractionLiteral':
// Decompress most common types
case 'i':
return {type: 'IntegerLiteral', value: BigInt(object.v)};
case 'f':
return {
type,
numerator: BigInt(object.numerator),
denominator: BigInt(object.denominator),
type: 'FractionLiteral',
numerator: BigInt(object.n),
denominator: BigInt(object.d),
};
case 'c':
return {
type: 'CentsLiteral',
sign: object.s,
whole: BigInt(object.w),
fractional: object.f,
exponent: object.e,
real: object.r,
};
case 'n':
return {
type: 'NedjiLiteral',
numerator: object.n,
denominator: object.d,
equaveNumerator: object.p,
equaveDenominator: object.q,
};
// Revive BigInts
case 'DecimalLiteral':
return {...object, whole: BigInt(object.whole)};
case 'RadicalLiteral':
return {
type,
argument: Fraction.reviver('argument', object.argument),
exponent: Fraction.reviver('exponent', object.exponent),
};
case 'CentsLiteral':
return {...object, whole: BigInt(object.whole)};
case 'SquareSuperparticular':
return {
type,
start: BigInt(object.start),
end: object.end && BigInt(object.end),
};
case 'StepLiteral':
case 'NedjiLiteral':
case 'CentLiteral':
case 'ReciprocalCentLiteral':
case 'NotANumberLiteral':
Expand All @@ -1254,5 +1283,10 @@ export function literalFromJSON(object: any): IntervalLiteral | undefined {
case 'SparseOffsetVal':
case 'WartsLiteral':
return object;
case 'IntegerLiteral':
case 'FractionLiteral':
case 'NedjiLiteral':
case 'CentsLiteral':
throw new Error('Unexpected uncompressed literal.');
}
}
32 changes: 16 additions & 16 deletions src/interval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,20 +246,20 @@ export class Interval {
value.type === 'Interval'
) {
let monzo: TimeMonzo | TimeReal;
if (value.value.type === 'TimeMonzo') {
monzo = TimeMonzo.reviver('value', value.value);
if (value.v.type === 'TimeMonzo') {
monzo = TimeMonzo.reviver('value', value.v);
} else {
monzo = TimeReal.reviver('value', value.value);
monzo = TimeReal.reviver('value', value.v);
}
const result = new Interval(
monzo,
value.domain,
value.steps,
literalFromJSON(value.node)
value.d ? 'logarithmic' : 'linear',
value.s,
literalFromJSON(value.n)
);
result.label = value.label;
result.color = value.color && new Color(value.color);
result.trackingIds = new Set(value.trackingIds);
result.label = value.l;
result.color = value.c && new Color(value.c);
result.trackingIds = new Set(value.t);
return result;
}
return value;
Expand All @@ -272,13 +272,13 @@ export class Interval {
toJSON(): any {
return {
type: 'Interval',
value: this.value.toJSON(),
domain: this.domain,
steps: this.steps,
label: this.label,
color: this.color && this.color.value,
node: literalToJSON(this.node),
trackingIds: Array.from(this.trackingIds),
v: this.value.toJSON(),
d: this.domain === 'linear' ? 0 : 1,
s: this.steps,
l: this.label,
c: this.color && this.color.value,
n: literalToJSON(this.node),
t: Array.from(this.trackingIds),
};
}

Expand Down
28 changes: 15 additions & 13 deletions src/monzo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
monzoToCents,
sum,
primeFactorize,
UnsignedFraction,
} from 'xen-dev-utils';

import {
Expand Down Expand Up @@ -237,15 +236,15 @@ export class TimeReal {
value !== null &&
value.type === 'TimeReal'
) {
let v: number | string = value.value;
let v: number | string = value.v;
if (v === 'nan') {
v = NaN;
} else if (v === 'inf') {
v = Infinity;
} else if (v === '-inf') {
v = -Infinity;
}
return new TimeReal(value.timeExponent, v as number);
return new TimeReal(value.t, v as number);
}
return value;
}
Expand All @@ -267,8 +266,8 @@ export class TimeReal {

return {
type: 'TimeReal',
timeExponent: this.timeExponent,
value,
t: this.timeExponent,
v: value,
};
}

Expand Down Expand Up @@ -1270,11 +1269,12 @@ export class TimeMonzo {
value !== null &&
value.type === 'TimeMonzo'
) {
const timeExponent = Fraction.reviver('timeExponent', value.timeExponent);
const primeExponents = (value.primeExponents as UnsignedFraction[]).map(
(component, i) => Fraction.reviver(i.toString(), component)
);
const residual = Fraction.reviver('residual', value.residual);
const timeExponent = Fraction.reviver('t', value.t);
const primeExponents: Fraction[] = [];
for (let i = 0; i < value.p.length; i += 2) {
primeExponents.push(new Fraction(value.p[i], value.p[i + 1]));
}
const residual = Fraction.reviver('r', value.r);
return new TimeMonzo(timeExponent, primeExponents, residual);
}
return value;
Expand All @@ -1287,9 +1287,11 @@ export class TimeMonzo {
toJSON() {
return {
type: 'TimeMonzo',
timeExponent: this.timeExponent.toJSON(),
primeExponents: this.primeExponents.map(component => component.toJSON()),
residual: this.residual.toJSON(),
t: this.timeExponent.toJSON(),
p: this.primeExponents
.map(component => [component.s * component.n, component.d])
.flat(),
r: this.residual.toJSON(),
};
}

Expand Down

0 comments on commit 6799f85

Please sign in to comment.