Skip to content

Commit

Permalink
Implement automos
Browse files Browse the repository at this point in the history
  • Loading branch information
frostburn committed May 12, 2024
1 parent f0b52f0 commit e34adf4
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 5 deletions.
3 changes: 3 additions & 0 deletions documentation/BUILTIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ Calculate atan2(y, x) which is the angle between (1, 0) and (x, y), chosen to li
### atanXY(*x*, *y*)
Calculate atanXY(x, y) = atan2(y, x) which is the angle between (1, 0) and (x, y), chosen to lie in (−π; π], positive anticlockwise.

### automos()
If the current scale is empty, generate absolute Diamond-mos notation based on the current config.

### bleach(*interval*)
Get rid of interval coloring and label.

Expand Down
11 changes: 11 additions & 0 deletions documentation/intermediate-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,17 @@ The accidental `&` (read "am") raises pitch by `L - s` while its opposite `@` (r

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

#### Auto-MOS
The easiest way to generate Diamond-mos notation for one equave is to call the `automos()` helper.
```c
"Specific mode (Anti-phrygian) of specific hardness of octave-equivalent Antidiatonic"
MOS 4333433
J4 = 263 Hz
automos()
```

A tool such as Scale Workshop may do this automatically as `automos()` doesn't do anything if the scale isn't empty or if `MOS` is undeclared. (It's possible to use `MOS niente` to undeclare an existing `MOS`.)

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

Expand Down
7 changes: 2 additions & 5 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {SonicWeaveValue} from './stdlib';
import {Interval} from './interval';
import {TimeMonzo} from './monzo';
import {ZERO} from './utils';
import {MosConfig} from './diamond-mos';
import {MosConfig, scaleMonzos} from './diamond-mos';
import {stepString} from './words';

/**
Expand Down Expand Up @@ -168,10 +168,7 @@ export class RootContext {
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 monzos = scaleMonzos(this.mosConfig);
const ss = stepString(monzos);
let large: TimeMonzo;
let small: TimeMonzo;
Expand Down
8 changes: 8 additions & 0 deletions src/diamond-mos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ export type AbsoluteMosPitch = {
octave: number;
};

export function scaleMonzos(config: MosConfig) {
const entries = Array.from(config.scale);
entries.sort((a, b) => a[0].localeCompare(b[0]));
const monzos = entries.map(entry => entry[1]);
monzos.push(monzos.shift()!.mul(config.equave) as TimeMonzo);
return monzos;
}

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;
Expand Down
22 changes: 22 additions & 0 deletions src/parser/__tests__/source.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1630,4 +1630,26 @@ describe('SonicWeave parser', () => {
`);
expect(scale).toEqual(['1\\8<3>', '3\\8<3>', '1\\2<3>']);
});

it('generates automos', () => {
const scale = parseSource(`
MOS 4L 3s 3|3
J4 = 100z
automos()
map(str)
`);
expect(scale.map(i => i.label)).toEqual([
'K♮4',
'L♮4',
'M♮4',
'N♮4',
'O♮4',
'P♮4',
'J♮5',
]);
expect(scale.map(i => i.valueOf())).toEqual([
113.4312522195463, 120.80894444044476, 137.03509847201238,
145.94801056814464, 165.55065597696213, 176.31825099920434, 200,
]);
});
});
33 changes: 33 additions & 0 deletions src/stdlib/builtin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
primeFactorize,
nthPrime as xduNthPrime,
primeRange as xduPrimeRange,
mmod,
} from 'xen-dev-utils';
import {Color, Interval, Val} from '../interval';
import {
Expand Down Expand Up @@ -71,6 +72,7 @@ import {
factorColor as pubFactorColor,
compare,
} from './public';
import {MosNominal, scaleMonzos} from '../diamond-mos';
const {version: VERSION} = require('../../package.json');

// === Library ===
Expand Down Expand Up @@ -1926,6 +1928,36 @@ function keepUnique(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) {
keepUnique.__doc__ = 'Only keep unique intervals in the current/given scale.';
keepUnique.__node__ = builtinNode(keepUnique);

function automos(this: ExpressionVisitor) {
const scale = this.currentScale;
if (!this.rootContext?.mosConfig || scale.length) {
return;
}
const J4 = this.rootContext.C4;
const monzos = scaleMonzos(this.rootContext.mosConfig).map(m => J4.mul(m));
return monzos.map(
(m, i) =>
new Interval(m, 'logarithmic', 0, {
type: 'AbsoluteFJS',
pitch: {
type: 'AbsolutePitch',
nominal: String.fromCharCode(
'J'.charCodeAt(0) + mmod(i + 1, monzos.length)
) as MosNominal,
accidentals: [{accidental: '♮', fraction: ''}],
octave: i === monzos.length - 1 ? 5 : 4,
},
ups: 0,
lifts: 0,
superscripts: [],
subscripts: [],
})
);
}
automos.__doc__ =
'If the current scale is empty, generate absolute Diamond-mos notation based on the current config.';
automos.__node__ = builtinNode(automos);

function reverse(this: ExpressionVisitor, scale?: SonicWeavePrimitive[]) {
scale ??= this.currentScale;
scale.reverse();
Expand Down Expand Up @@ -2551,6 +2583,7 @@ export const BUILTIN_CONTEXT: Record<string, Interval | SonicWeaveFunction> = {
sorted,
keepUnique,
uniquesOf,
automos,
reverse,
reversed,
pop,
Expand Down

0 comments on commit e34adf4

Please sign in to comment.