Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for real values in MOS declaration #340

Merged
merged 1 commit into from
Jun 4, 2024
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
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
Loading