Skip to content

Commit

Permalink
MOS declaration
Browse files Browse the repository at this point in the history
ref #312
  • Loading branch information
frostburn committed May 11, 2024
1 parent be1a280 commit 39c5b33
Show file tree
Hide file tree
Showing 15 changed files with 893 additions and 62 deletions.
114 changes: 114 additions & 0 deletions documentation/intermediate-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,120 @@ G4 // 3/2
C5 // 2/1
```

## MOS declaration
[Moment of symmetry scales](https://en.xen.wiki/w/MOS_scale) are generalizations of the usual diatonic scale where instead of a perfect fifth the scale is generated by stacking some other interval. The scale might also repeat at a fraction of the octave or some other interval.

While the intervals of such scales are readily obtained using the `mos(countL, countS)` or `rank2(generator)` helpers there's a complete system for generating relative and absolute notation for these scales.

MOS can be declared in many ways.
```c
"Brightest mode (Ryonian) of basic octave-equivalent Archeotonic"
MOS 6L 1s
```

```c
"Specific mode (Nightmare) of basic octave-equivalent Ekic"
MOS LLsLLLsL
```

```c
"Specific mode (Anti-phrygian) of specific hardness of octave-equivalent Antidiatonic"
MOS 4333433
```

```c
"Specific mode (Salmon) of specific hardness of octave-equivalent Pine"
MOS 43, 43, 10, 43, 43, 43, 43, 43
```

```c
"Brightest mode of the tritave-equivalent Lambda scale"
MOS 4L 5s <3>
```

```c
"Gil mode of octave-equivalent Mosh with large step equal to 9/8"
MOS {
3L 4s 5|1
L = 9/8
}
```
### 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.
| Name | Pattern |
| -------------------- | ------------------------------------ |
| Counts with UDP | `nL ns up\|down(period)? <equave>?` |
| Small integers | `[x\|y]+ <equave>?` |
| Large integers | *(comma-separated list of integers)* |
| Abstract | `[L\|s]+ <equave>?` |
| Hardness declaration | `hardness = expr` |
| Equave declaration | `equave = expr` |
| Large declaration | `L = expr` |
| Small declaration | `s = expr` |

### Diamond-mos notation
Once `MOS` has been declared [Diamond-mos notation](https://en.xen.wiki/w/Diamond-mos_notation) becomes available.

The absolute pitch `J4` always corresponds to `C4` and nominals K, L, M, N, etc. follow according to the declared mode. `J5` is an equave above `J4`.

Diamond-mos nominals are second-class syntax so you'll need to dodge previous syntax: `M_3` instead of `M3` (major third), `P_4` instead of `P4` (perfect fourth) and `S_9` instead of `S9` (square superparticular). The recommendation is to always use a natural sign (`_` or ``) with Diamond-mos pitches.

```c
"Brightest mode (Ryonian) of basic octave-equivalent Archeotonic"
MOS 6L 1s

J_4 = 263 Hz
K_4 // 2\13
L_4 // 4\13
M_4 // 6\13
N_4 // 8\13
O_4 // 10\13
P_4 // 12\13
J_5 // 13\13
```

The accidental `&` (read "am") raises pitch by `L - s` while its opposite `@` (read "at") correspondingly lowers pitch by the same amount.

The accidental `e` (read "semiam") raises pitch by a half *am* while `a` (read "semiat") corresponingly lowest pitch by a half *at*.

### TAMNAMS relative intervals
You can also [name relative intervals](https://en.xen.wiki/w/TAMNAMS#Naming_mos_intervals) after declaring `MOS`.

The indexing starts from 0 instead of 1.

```c
"Specific mode (Nightmare) of basic octave-equivalent Ekic"
MOS LLsLLLsL

P0ms = 333 Hz // Same as 1 = 333 Hz
P1ms // 2\14
M2ms // 4\14
P3ms // 5\14
P4ms // 7\14
P5ms // 9\14
M6ms // 11\14
P7ms // 12\14
P8ms // 14\14
```

Neutral (e.g. `n2ms`) and mid intervals (e.g. `n1ms`) are also available. (Semi-)augmented intervals have similar logic to their diatonic counterparts: Augmentation is counted from major upwards and diminishment from minor downwards unless the central interval has perfect quality.

#### Exception for nL ns
When there's only one other interval per period the bright (wide) variant is designated major while the dark (narrow) variant is designated minor.
```c
"A scale spelled using relative Triwood intervals"
MOS 3L 3s

P0ms = 333 Hz
M1ms // 2\9
P2ms // 3\9
m3ms // 4\9
P4ms // 6\9
P6ms // 9\9
```

## Next steps

[Advanced DSL](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/advanced-dsl.md)
22 changes: 5 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"clean": "gts clean",
"compile-sw2-parser": "peggy src/grammars/scale-workshop-2.pegjs -o src/scale-workshop-2-ast.js",
"compile-chord-parser": "peggy src/grammars/sonic-weave-chord.pegjs src/grammars/base.pegjs -o src/parser/sonic-weave-chord.js",
"compile-sonic-weave-parser": "peggy src/grammars/sonic-weave.pegjs src/grammars/base.pegjs -o src/parser/sonic-weave-ast.js",
"compile-sonic-weave-parser": "peggy src/grammars/sonic-weave.pegjs src/grammars/mos.pegjs src/grammars/base.pegjs -o src/parser/sonic-weave-ast.js",
"compile-paren-counter": "peggy src/grammars/paren-counter.pegjs src/grammars/base.pegjs -o src/parser/paren-counter.js",
"compile-parsers": "npm run compile-sonic-weave-parser && npm run compile-chord-parser && npm run compile-sw2-parser && npm run compile-paren-counter",
"precompile": "npm run compile-parsers",
Expand Down Expand Up @@ -67,7 +67,7 @@
"vitest": "^1.4.0"
},
"dependencies": {
"moment-of-symmetry": "^0.4.2",
"moment-of-symmetry": "^0.5.1",
"xen-dev-utils": "^0.7.0"
},
"engines": {
Expand Down
67 changes: 67 additions & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ export type Statement =
| ExportAllStatement
| ImportStatement
| ImportAllStatement
| MosDeclaration
| ThrowStatement
| BreakStatement
| ContinueStatement
Expand Down Expand Up @@ -451,6 +452,72 @@ export type Expression =
| StringLiteral
| HarmonicSegment;

export type RationalEquave = {
numerator: number;
denominator: number;
};

export type AbstractStepPattern = {
type: 'AbstractStepPattern';
pattern: ('L' | 's')[];
equave: RationalEquave | null;
};

export type IntegerPattern = {
type: 'IntegerPattern';
pattern: number[];
equave: RationalEquave | null;
};

export type UpDownPeriod = {
type: 'UDP';
up: number;
down: number;
period: number | null;
};

export type PatternUpDownPeriod = {
type: 'PatternUpDownPeriod';
countLarge: number;
countSmall: number;
udp: UpDownPeriod | null;
equave: RationalEquave | null;
};

export type HardnessDeclaration = {
type: 'HardnessDeclaration';
value: Expression;
};

export type LargeDeclaration = {
type: 'LargeDeclaration';
value: Expression;
};

export type SmallDeclaration = {
type: 'SmallDeclaration';
value: Expression;
};

export type EquaveDeclaration = {
type: 'EquaveDeclaration';
value: Expression;
};

export type MosExpression =
| AbstractStepPattern
| IntegerPattern
| PatternUpDownPeriod
| HardnessDeclaration
| LargeDeclaration
| SmallDeclaration
| EquaveDeclaration;

export type MosDeclaration = {
type: 'MosDeclaration';
body: MosExpression[];
};

/**
* Convert an AST node to a string representation.
* @param node Expression node to convert.
Expand Down
31 changes: 31 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {SonicWeaveValue} from './stdlib';
import {Interval} from './interval';
import {TimeMonzo} from './monzo';
import {ZERO} from './utils';
import {MosConfig} from './diamond-mos';
import {stepString} from './words';

/**
* Root context of a SonicWeave runtime containing the scale title, root pitch, value of the 'up' inflection etc.
Expand Down Expand Up @@ -35,6 +37,7 @@ export class RootContext {
private C4_: TimeMonzo;
private up_: Interval;
private lift_: Interval;
private mosConfig_?: MosConfig;

/**
* Create a new root context.
Expand Down Expand Up @@ -85,6 +88,17 @@ export class RootContext {
this.breakFragiles();
}

/**
* MOS declaration result.
*/
get mosConfig() {
return this.mosConfig_;
}
set mosConfig(value: MosConfig | undefined) {
this.mosConfig_ = value;
this.breakFragiles();
}

/**
* Create an independent clone of the context useable as a cache of runtime state.
* @returns A {@link RootContext} in the same state as this one.
Expand Down Expand Up @@ -153,6 +167,23 @@ export class RootContext {
if (!this.lift.strictEquals(defaults.lift)) {
lines.push(`/ = ${this.lift.toString()}`);
}
if (this.mosConfig) {
const entries = Array.from(this.mosConfig.scale.entries());
entries.sort((a, b) => a[0].localeCompare(b[0]));
const monzos = entries.map(entry => entry[1]);
monzos.push(monzos.shift()!.mul(this.mosConfig.equave) as TimeMonzo);
const ss = stepString(monzos);
let large: TimeMonzo;
let small: TimeMonzo;
if (ss.startsWith('L')) {
large = monzos[0];
small = large.div(this.mosConfig.am) as TimeMonzo;
} else {
small = monzos[0];
large = small.mul(this.mosConfig.am) as TimeMonzo;
}
lines.push(`MOS {${ss};L=${large};s=${small}}`);
}
return lines.join('\n');
}

Expand Down
20 changes: 14 additions & 6 deletions src/diamond-mos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ export type MosDegree = {
* May define a non-MOS scale as a result of accidental or intentional misconfiguration.
*/
export type MosConfig = {
/**
* Current value of middle J.
*/
J4: TimeMonzo;
/**
* Interval of equivalence. The distance between J4 and J5.
*/
Expand Down Expand Up @@ -97,6 +93,7 @@ export type AbsoluteMosPitch = {

export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo {
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, []);
Expand Down Expand Up @@ -143,13 +140,24 @@ export function mosMonzo(node: MosStep, config: MosConfig): TimeMonzo {
} else if (quality === 'd' || quality === 'dim') {
inflection = inflection.div(config.semiam) as TimeMonzo;
}
if (quality === 'P') {
throw new Error(
`The mosstep ${baseDegree} does not have a perfect variant.`
);
}
} else if (quality === 'n') {
if (!mosDegree.mid) {
throw new Error('Missing mid mosstep quality.');
}
return mosDegree.mid;
} else if (quality === 'M' || quality === 'm') {
throw new Error(
`The mosstep ${baseDegree} does not have minor or major variants.`
);
}
return mosDegree.center.mul(inflection) as TimeMonzo;
return mosDegree.center
.mul(inflection)
.mul(config.period.pow(periods)) as TimeMonzo;
}

function mosInflection(
Expand Down Expand Up @@ -191,5 +199,5 @@ export function absoluteMosMonzo(
}
result = result.mul(fractionalInflection) as TimeMonzo;
}
return result;
return result.mul(config.equave.pow(node.octave - 4)) as TimeMonzo;
}
Loading

0 comments on commit 39c5b33

Please sign in to comment.