Skip to content

Commit

Permalink
Add support for real values in MOS declaration
Browse files Browse the repository at this point in the history
Fix automos infragility issues.

ref #328
  • Loading branch information
frostburn committed Jun 4, 2024
1 parent 6850b6d commit 02fc059
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 55 deletions.
2 changes: 1 addition & 1 deletion documentation/intermediate-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ MOS {
### MOS declaration syntax
The syntax for MOS declarations withing the curly brackets after `MOS` is as follows:

Below `n` is an integer, `x` and `y` are integers in the range from 1 to 9, and `expr` is any SonicWeave expression that evaluates to a radical interval. Syntax followed by a question mark is optional.
Below `n` is an integer, `x` and `y` are integers in the range from 1 to 9, and `expr` is any SonicWeave expression that evaluates to an interval. Syntax followed by a question mark is optional.
| Name | Pattern |
| -------------------- | ------------------------------------ |
| Counts with UDP | `nL ns up\|down(period)? <equave>?` |
Expand Down
50 changes: 22 additions & 28 deletions src/diamond-mos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,24 @@ import {
} from './pythagorean';
import {ZERO} from './utils';

type Monzo = TimeMonzo | TimeReal;

/**
* Base degree for a mosstep in a 0-indexed array.
*/
export type MosDegree = {
/**
* The perfect or neutral central interval.
*/
center: TimeMonzo;
center: Monzo;
/**
* Flag to indicate if the degree has minor and major variants.
*/
imperfect: boolean;
/**
* The lopsided neutral variant of a bright or dark generator.
*/
mid?: TimeMonzo;
mid?: Monzo;
};

/**
Expand All @@ -37,23 +39,23 @@ export type MosConfig = {
/**
* Interval of equivalence. The distance between J4 and J5.
*/
equave: TimeMonzo;
equave: Monzo;
/**
* Period of repetition.
*/
period: TimeMonzo;
period: Monzo;
/**
* Current value of the '&' accidental.
*/
am: TimeMonzo;
am: Monzo;
/**
* Current value of the 'e' accidental.
*/
semiam: TimeMonzo;
semiam: Monzo;
/**
* Relative scale from J onwards. Echelon depends on J. Use equave to reach higher octave numbers.
*/
scale: Map<string, TimeMonzo>;
scale: Map<string, Monzo>;
/**
* Intervals for relative notation. Use period to reach larger intervals.
*/
Expand All @@ -65,11 +67,11 @@ export type MosConfig = {
/**
* Value of the large step.
*/
large: TimeMonzo;
large: Monzo;
/**
* Value of the small step.
*/
small: TimeMonzo;
small: Monzo;
};

/**
Expand Down Expand Up @@ -125,7 +127,7 @@ export function scaleMonzos(config: MosConfig) {
: a[0].localeCompare(b[0])
);
const monzos = entries.map(entry => entry[1]);
monzos.push(monzos.shift()!.mul(config.equave) as TimeMonzo);
monzos.push(monzos.shift()!.mul(config.equave));
return monzos;
}

Expand All @@ -135,12 +137,12 @@ export function scaleMonzos(config: MosConfig) {
* @param config Result of a MOS declaration.
* @returns A relative time monzo.
*/
export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo {
export function mosMonzo(node: MosStep, config: MosConfig): Monzo {
const baseDegree = mmod(Math.abs(node.degree), config.degrees.length);
const periods = (node.degree - baseDegree) / config.degrees.length;
const mosDegree = config.degrees[baseDegree];
const quality = node.quality.quality;
let inflection = new TimeMonzo(ZERO, []);
let inflection: Monzo = new TimeMonzo(ZERO, []);
if (
quality === 'a' ||
quality === 'Â' ||
Expand All @@ -158,17 +160,14 @@ export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo {
if (node.quality.fraction !== '') {
const fraction = VULGAR_FRACTIONS.get(node.quality.fraction)!;
const fractionalInflection = inflection.pow(fraction);
if (fractionalInflection instanceof TimeReal) {
throw new Error('Failed to fractionally inflect mosstep.');
}
inflection = fractionalInflection;
}

for (const augmentation of node.augmentations ?? []) {
if (augmentation === 'd' || augmentation === 'dim') {
inflection = inflection.div(config.am) as TimeMonzo;
inflection = inflection.div(config.am);
} else {
inflection = inflection.mul(config.am) as TimeMonzo;
inflection = inflection.mul(config.am);
}
}

Expand All @@ -180,9 +179,9 @@ export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo {
quality === 'aug' ||
quality === 'Aug'
) {
inflection = inflection.mul(config.semiam) as TimeMonzo;
inflection = inflection.mul(config.semiam);
} else if (quality === 'd' || quality === 'dim') {
inflection = inflection.div(config.semiam) as TimeMonzo;
inflection = inflection.div(config.semiam);
}
if (quality === 'P') {
throw new Error(
Expand All @@ -199,9 +198,7 @@ export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo {
`The mosstep ${baseDegree} does not have minor or major variants.`
);
}
return mosDegree.center
.mul(inflection)
.mul(config.period.pow(periods)) as TimeMonzo;
return mosDegree.center.mul(inflection).mul(config.period.pow(periods));
}

function mosInflection(
Expand Down Expand Up @@ -234,7 +231,7 @@ function mosInflection(
export function absoluteMosMonzo(
node: AbsoluteMosPitch,
config: MosConfig
): TimeMonzo {
): Monzo {
if (!config.scale.has(node.nominal)) {
throw new Error(`Nominal ${node.nominal} is unassigned.`);
}
Expand All @@ -244,10 +241,7 @@ export function absoluteMosMonzo(

const fraction = VULGAR_FRACTIONS.get(accidental.fraction)!;
const fractionalInflection = inflection.pow(fraction);
if (fractionalInflection instanceof TimeReal) {
throw new Error('Failed to fracture mos accidental.');
}
result = result.mul(fractionalInflection) as TimeMonzo;
result = result.mul(fractionalInflection);
}
return result.mul(config.equave.pow(node.octave - 4)) as TimeMonzo;
return result.mul(config.equave.pow(node.octave - 4));
}
2 changes: 1 addition & 1 deletion src/fjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ export function getInflection(
}

export function inflect(
pythagorean: TimeMonzo,
pythagorean: TimeMonzo | TimeReal,
superscripts: FJSInflection[],
subscripts: FJSInflection[]
) {
Expand Down
20 changes: 20 additions & 0 deletions src/parser/__tests__/source.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,26 @@ describe('SonicWeave parser', () => {
]);
});

it('supports irrational hardness in MOS declaration', () => {
const scale = expand(`
MOS {
5L 2s
hardness = PI
}
automos()
MOS niente
`);
expect(scale).toEqual([
'212.8935511816436r¢',
'425.7871023632872r¢',
'638.6806535449307r¢',
'706.446775590823r¢',
'919.3403267724666r¢',
'1132.23387795411r¢',
'1200.0000000000025r¢',
]);
});

it('supports radicals in SOV', () => {
const scale = expand(`
9/8
Expand Down
38 changes: 14 additions & 24 deletions src/parser/mos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@ import {Interval} from '../interval';
import {MosConfig, MosDegree} from '../diamond-mos';
import {ONE, TWO, ZERO} from '../utils';

type Monzo = TimeMonzo | TimeReal;

const TWO_MONZO = new TimeMonzo(ZERO, [ONE]);

function realize(mosMonzo: MosMonzo, large: TimeMonzo, small: TimeMonzo) {
return large.pow(mosMonzo[0]).mul(small.pow(mosMonzo[1])) as TimeMonzo;
function realize(mosMonzo: MosMonzo, large: Monzo, small: Monzo) {
return large.pow(mosMonzo[0]).mul(small.pow(mosMonzo[1]));
}

/**
* Pun on MosVisitor.
*/
export class Tardigrade {
subVisitor: ExpressionVisitor;
equave?: TimeMonzo;
hardness?: TimeMonzo | TimeReal;
large?: TimeMonzo;
small?: TimeMonzo;
equave?: Monzo;
hardness?: Monzo;
large?: Monzo;
small?: Monzo;
pattern?: string;

constructor(subVisitor: ExpressionVisitor) {
Expand All @@ -56,8 +58,8 @@ export class Tardigrade {
const N = new Fraction(this.pattern.length);
const countL = new Fraction((this.pattern.match(/L/g) ?? []).length);
const countS = N.sub(countL);
let small: TimeMonzo | TimeReal;
let large: TimeMonzo | TimeReal;
let small: Monzo;
let large: Monzo;
if (this.equave) {
if (this.hardness) {
if (this.hardness.valueOf() === Infinity) {
Expand All @@ -78,14 +80,10 @@ export class Tardigrade {
}
} else if (this.large) {
large = this.large;
small = this.equave
.div(large.pow(countL))
.pow(countS.inverse()) as TimeMonzo;
small = this.equave.div(large.pow(countL)).pow(countS.inverse());
} else if (this.small) {
small = this.small;
large = this.equave
.div(small.pow(countS))
.pow(countL.inverse()) as TimeMonzo;
large = this.equave.div(small.pow(countS)).pow(countL.inverse());
} else {
// Assume basic
small = this.equave.pow(countL.mul(TWO).add(countS).inverse());
Expand Down Expand Up @@ -150,15 +148,10 @@ export class Tardigrade {
throw new Error('Inconsistent MOS declaration.');
}

if (large instanceof TimeReal || small instanceof TimeReal) {
throw new Error('MOS declaration must remain radical.');
}

const am = large.div(small) as TimeMonzo;
const semiam = am.sqrt() as TimeMonzo;
const r = (m: MosMonzo) =>
realize(m, large as TimeMonzo, small as TimeMonzo);
const scale = notation.scale as unknown as Map<string, TimeMonzo>;
const r = (m: MosMonzo) => realize(m, large, small);
const scale = notation.scale as unknown as Map<string, Monzo>;
for (const [key, value] of notation.scale) {
scale.set(key, r(value));
}
Expand Down Expand Up @@ -263,9 +256,6 @@ export class Tardigrade {
if (node.type === 'HardnessDeclaration' && value.valueOf() === Infinity) {
return (this.hardness = value.value);
}
if (!(value.value instanceof TimeMonzo)) {
throw new Error(`${node.type} must evaluate to a radical.`);
}
switch (node.type) {
case 'HardnessDeclaration':
return (this.hardness = value.value);
Expand Down
4 changes: 3 additions & 1 deletion src/stdlib/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1971,7 +1971,7 @@ function automos(this: ExpressionVisitor) {
this.spendGas(this.rootContext.mosConfig.scale.size);
const J4 = this.rootContext.C4;
const monzos = scaleMonzos(this.rootContext.mosConfig).map(m => J4.mul(m));
return monzos.map(
const result = monzos.map(
(m, i) =>
new Interval(m, 'logarithmic', 0, {
type: 'AbsoluteFJS',
Expand All @@ -1987,6 +1987,8 @@ function automos(this: ExpressionVisitor) {
subscripts: [],
})
);
this.rootContext.fragiles.push(...result);
return result;
}
automos.__doc__ =
'If the current scale is empty, generate absolute Diamond-mos notation based on the current config.';
Expand Down

0 comments on commit 02fc059

Please sign in to comment.