Skip to content

Commit

Permalink
Implement sw$ tag for scales
Browse files Browse the repository at this point in the history
Document the template tags a little better.

ref #173
  • Loading branch information
frostburn committed May 12, 2024
1 parent 9d8eba9 commit a4adaae
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 22 deletions.
49 changes: 46 additions & 3 deletions documentation/tag.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,53 @@ This document describes the `sw` and `swr` tags used for writing SonicWeave insi
import {sw} from 'sonic-weave';

const myFifth = sw`3/2`;
console.log(myFifth.totalCents()) // 701.9550008653875
console.log(myFifth.totalCents()); // 701.9550008653875

const myOctave = sw`${2}`;
console.log(myOctave.totalCents()) // 1200
console.log(myOctave.totalCents()); // 1200
```

TODO: Actual description
## Raison d'être
Constructing intervals using the [JavaScript API](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/package.md) is somewhat tedious so you can use [SonicWeave DSL](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/dsl.md) instead when writing JS scripts for generating and analysing microtonal scales.

## The sw tag
The `sw` tag evaluates escapes such as `\n` for a newline inside the tag:
```ts
import {sw} from 'sonic-weave';

const ratio = sw`const fif = 3/2\nconst third = 6/5\nthird * fif`;
ratio.toFraction(); // new Fraction(9, 5)
ratio.toFraction().toFraction(); // "9/5"
```

This means that backslashes must be entered doubled (`\\`). Luckily the binary operator `sof` and the unary operator `drop` exist to make the meaning of your code more clear at a glance.
```ts
import {sw} from 'sonic-weave';

const minorSeventh = sw`
const fif = 7 sof 12;
const third = 3 sof 12;
third + fif;
`;
minorSeventh.totalCents(); // 1000
```

## The swr tag
The `swr` tag uses `String.raw` semantics which makes backslash fractions a.k.a. NEDO easier to enter:
```ts
import {swr} from 'sonic-weave';

const minorSeventh = swr`
const fif = 7\12;
const third = 3\12;
third + fif;
`;
minorSeventh.totalCents(); // 1000
```

## The sw$ and sw$r tags
The `sw$` tag and it's raw `sw$r` counterpart produce arrays of intervals.
```ts
const tet5 = sw$`tet(5)`;
console.log(tet5.map(interval => interval.totalCents())); // [240, 480, 720, 960, 1200]
```
14 changes: 13 additions & 1 deletion src/parser/__tests__/tag.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {describe, it, expect} from 'vitest';
import {sw, swr} from '../../parser';
import {sw, sw$, sw$r, swr} from '../../parser';
import {Color, Interval, Val} from '../../interval';
import {Fraction} from 'xen-dev-utils';

Expand Down Expand Up @@ -164,3 +164,15 @@ describe('SonicWeave raw template tag', () => {
expect(interval.totalCents()).toBe(700);
});
});

describe('SonicWeave scale template tags', () => {
it('has an escaping variant', () => {
const scale = sw$`rank2(7\\12, 4)`;
expect(scale.map(i => i.totalCents())).toEqual([200, 400, 700, 900, 1200]);
});

it('has a raw variant', () => {
const scale = sw$r`rank2(7\12, 4)`;
expect(scale.map(i => i.totalCents())).toEqual([200, 400, 700, 900, 1200]);
});
});
81 changes: 63 additions & 18 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,14 @@ function convert(value: any): SonicWeaveValue {

/**
* Create a tag for [templates literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) to evaluate SonicWeave programs inside JavaScript code.
* @param expression Whether or not this tag is intended for evaluation single expressions or full scales producing arrays of {@link Interval} instances.
* @param includePrelude Whether or not to include the extended standard library. Passing in `false` results in a faster start-up time.
* @param extraBuiltins Custom builtins callable inside the SonicWeave program.
* @param escapeStrings If `true` all escape sequences are evaluated before interpreting the literal as a SonicWeave program.
* @returns A tag that can be attached to template literals in order to evaluate them.
*/
export function createTag(
expression = true,
includePrelude = true,
extraBuiltins?: Record<string, SonicWeaveValue>,
escapeStrings = false
Expand All @@ -232,29 +234,34 @@ export function createTag(
source += ${i}` + fragments[i + 1];
}
const program = parseAST(source);
for (const statement of program.body.slice(0, -1)) {
const interrupt = visitor.visit(statement);
if (interrupt) {
throw new Error(`Illegal ${interrupt.type}.`);
if (expression) {
for (const statement of program.body.slice(0, -1)) {
const interrupt = visitor.visit(statement);
if (interrupt) {
throw new Error(`Illegal ${interrupt.type}.`);
}
}
if (visitor.deferred.length) {
throw new Error(
'Deferred actions not allowed when evaluating tagged templates.'
);
}
const finalStatement = program.body[program.body.length - 1];
if (finalStatement.type !== 'ExpressionStatement') {
throw new Error(`Expected expression. Got ${finalStatement.type}.`);
}
const subVisitor = visitor.createExpressionVisitor();
return subVisitor.visit(finalStatement.expression);
} else {
visitor.executeProgram(program);
return visitor.currentScale;
}
if (visitor.deferred.length) {
throw new Error(
'Deferred actions not allowed when evaluating tagged templates.'
);
}
const finalStatement = program.body[program.body.length - 1];
if (finalStatement.type !== 'ExpressionStatement') {
throw new Error(`Expected expression. Got ${finalStatement.type}.`);
}
const subVisitor = visitor.createExpressionVisitor();
return subVisitor.visit(finalStatement.expression);
}
return tag;
}

/**
* Tag for evaluating [templates literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) as SonicWeave programs.
* Tag for evaluating [templates literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) as SonicWeave expressions.
* Has raw (unescaped) semantics.
*
* Example:
Expand All @@ -270,7 +277,26 @@ Object.defineProperty(swr, 'name', {
});

/**
* Tag for evaluating [templates literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) as SonicWeave programs.
* Tag for evaluating [templates literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) as SonicWeave scales.
* Has raw (unescaped) semantics.
*
* Example:
* ```ts
* const pentatonic = sw$r`rank2(7\12, 4)`;
* console.log(pentatonic.map(interval => interval.totalCents())); // [200, 400, 700, 900, 1200]
* ```
*/
export const sw$r = createTag(false) as (
strings: TemplateStringsArray,
...args: any[]
) => Interval[];
Object.defineProperty(swr, 'name', {
value: 'sw$r',
enumerable: false,
});

/**
* Tag for evaluating [templates literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) as SonicWeave expressions.
* Evaluates escapes before interpreting the program so e.g. a double backslash means only a single backslash within the program.
*
* Example:
Expand All @@ -279,8 +305,27 @@ Object.defineProperty(swr, 'name', {
* console.log(interval.totalCents()); // 700
* ```
*/
export const sw = createTag(true, undefined, true);
export const sw = createTag(true, true, undefined, true);
Object.defineProperty(sw, 'name', {
value: 'sw',
enumerable: false,
});

/**
* Tag for evaluating [templates literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates) as SonicWeave scales.
* Evaluates escapes before interpreting the program so e.g. a double backslash means only a single backslash within the program.
*
* Example:
* ```ts
* const pentatonic = sw$`rank2(7\\12, 4)`;
* console.log(pentatonic.map(interval => interval.totalCents())); // [200, 400, 700, 900, 1200]
* ```
*/
export const sw$ = createTag(false, true, undefined, true) as (
strings: TemplateStringsArray,
...args: any[]
) => Interval[];
Object.defineProperty(swr, 'name', {
value: 'sw$',
enumerable: false,
});

0 comments on commit a4adaae

Please sign in to comment.