Skip to content

Commit

Permalink
Implement popped scale magic syntax
Browse files Browse the repository at this point in the history
ref #333
  • Loading branch information
frostburn committed Jun 2, 2024
1 parent 572bccd commit 94878d1
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 14 deletions.
3 changes: 3 additions & 0 deletions documentation/advanced-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ Blocks start with a curly bracket `{`, have their own instance of a current scal
### Parent scale
The current scale of the parent block can be accessed using `$$`.

### Popped parent scale
A copy of the current scale of the parent block can be obtained using `££` (or the ASCII variant `pop$$`) while simultaneously clearing the original.

## Defer
Defer is used to execute a statement while exiting the current block.

Expand Down
11 changes: 11 additions & 0 deletions documentation/intermediate-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ Inspired by [NumPy](https://numpy.org/), most functions that accept intervals ma

The previous example is equivalent to `$ = simplify([10/8, 12/8, 16/8])`.

### Popped scale
Using the current scale `$` as a variable often leads to duplicated data. There's a magic variable `£` (or the ASCII variant `pop$`) that obtains a copy of the current scale and clears the existing scale when used. Very handy with [vector broadcasting](#vector-broadcasting).

```ocaml
10/9
4/3
16/9
£ * 9/8
```
Results in `$ = [5/4, 3/2, 2]`.

### Implicit tempering
In addition to musical intervals SonicWeave features something known as *vals* which are mainly used for converting scales in just intonation to equally tempered scales.

Expand Down
8 changes: 8 additions & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,11 @@ export type Identifier = {
id: string;
};

export type PopScale = {
type: 'PopScale';
parent: boolean;
};

export type TemplateArgument = {
type: 'TemplateArgument';
index: number;
Expand Down Expand Up @@ -443,6 +448,7 @@ export type Expression =
| FalseLiteral
| ColorLiteral
| Identifier
| PopScale
| TemplateArgument
| EnumeratedChord
| Range
Expand Down Expand Up @@ -555,6 +561,8 @@ export function expressionToString(node: Expression) {
return 'niente';
case 'Identifier':
return node.id;
case 'PopScale':
return node.parent ? '££' : '£';
case 'TemplateArgument':
return ${node.index}`;
case 'StringLiteral':
Expand Down
19 changes: 19 additions & 0 deletions src/grammars/sonic-weave.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
'not',
'of',
'or',
'pop$',
'pop$$',
'rd',
'rdc',
'return',
Expand Down Expand Up @@ -183,6 +185,8 @@ NoneToken = @'niente' !IdentifierPart
NotToken = @'not' !IdentifierPart
OfToken = @'of' !IdentifierPart
OrToken = @'or' !IdentifierPart
PopScaleToken = @'pop$' !IdentifierPart
PopParentToken = @'pop$$' !IdentifierPart
ReduceToken = @'rd' !IdentifierPart
ReduceCeilingToken = @'rdc' !IdentifierPart
ReturnToken = @'return' !IdentifierPart
Expand Down Expand Up @@ -993,6 +997,7 @@ Quantity
/ SparseOffsetVal
/ ReciprocalCentLiteral
/ ReciprocalLogarithmicHertzLiteral
/ PopScale
/ MonzoLiteral
/ ValLiteral
/ DownExpression
Expand Down Expand Up @@ -1251,6 +1256,20 @@ ReciprocalCentLiteral
ReciprocalLogarithmicHertzLiteral
= '' { return { type: 'ReciprocalLogarithmicHertzLiteral' }; }
PopScale
= ('££' / PopParentToken) {
return {
type: 'PopScale',
parent: true,
};
}
/ ('£' / PopScaleToken) {
return {
type: 'PopScale',
parent: false,
};
}
NoneLiteral
= NoneToken { return { type: 'NoneLiteral' }; }
Expand Down
37 changes: 37 additions & 0 deletions src/parser/__tests__/source.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1844,4 +1844,41 @@ describe('SonicWeave parser', () => {
visitor.visit(ast.body[0]);
expect(() => visitor.visit(ast.body[1])).toThrow();
});

it('can pop the current scale as a magic variable', () => {
const scale = expand(`
5/4
3/2
2/1
sorted(%£ rdc pop$[-1])
`);
expect(scale).toEqual(['4/3', '8/5', '2']);
});

it('can pop the parent scale as a magic variable', () => {
const scale = expand(String.raw`{
fn poppyStack(array = ££) {
"Cumulatively stack the current/given intervals on top of each other.";
array;
let i = 0r;
const len = real(length($));
while (++i < len)
$[i] ~*= $[i-1r];
}
2 \ 12
2 \ 12
1 \ 12
poppyStack();
$[-1] + poppyStack([2, 2, 2, 1] \ 12);
}`);
expect(scale).toEqual([
'2\\12',
'4\\12',
'5\\12',
'7\\12',
'9\\12',
'11\\12',
'12\\12',
]);
});
});
22 changes: 22 additions & 0 deletions src/parser/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,26 @@ export class ExpressionVisitor {
this.parent.currentScale = scale;
}

popScale(parent: boolean): Interval[] {
if (parent) {
if (!this.mutables.has('££')) {
const scale = this.get('$$') as Interval[];
if (!Array.isArray(scale)) {
throw new Error('Context corruption detected.');
}
this.mutables.set('££', [...scale]);
scale.length = 0;
}
return this.mutables.get('££') as Interval[];
}
if (!this.mutables.has('£')) {
const scale = this.currentScale;
this.mutables.set('£', [...scale]);
scale.length = 0;
}
return this.mutables.get('£') as Interval[];
}

spendGas(amount?: number) {
this.parent.spendGas(amount);
}
Expand Down Expand Up @@ -358,6 +378,8 @@ export class ExpressionVisitor {
return new Color(node.value);
case 'Identifier':
return this.visitIdentifier(node);
case 'PopScale':
return this.popScale(node.parent);
case 'EnumeratedChord':
return this.visitEnumeratedChord(node);
case 'Range':
Expand Down
28 changes: 14 additions & 14 deletions src/stdlib/prelude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,9 +478,9 @@ riff mos(numberOfLargeSteps, numberOfSmallSteps, sizeOfLargeStep = 2, sizeOfSmal
mosSubset(numberOfLargeSteps, numberOfSmallSteps, sizeOfLargeStep, sizeOfSmallStep, up, down);
const divisions = abs $[-1];
if (equave == 2)
step => step \\ divisions;
£ \\ divisions;
else
step => step \\ divisions ed equave;
£ \\ divisions ed equave;
}
riff rank2(generator, up, down = 0, period = 2, numPeriods = 1, generatorSizeHint = niente, periodSizeHint = niente) {
Expand Down Expand Up @@ -529,7 +529,7 @@ riff wellTemperament(commaFractions, comma = 81/80, down = 0, generator = 3/2, p
1;
generator ~* comma ~^ commaFractions;
stack();
i => i ~/ $[down] rdc period;
£ ~/ £[down] rdc period;
sort();
period vor pop();
}
Expand Down Expand Up @@ -562,7 +562,7 @@ riff parallelotope(basis, ups = niente, downs = niente, equave = 2, basisSizeHin
if (basisSizeHints == niente and equaveSizeHint == niente) {
$ = $$;
i => i ~rdc equave;
£ ~rdc equave;
sort();
return $;
}
Expand Down Expand Up @@ -710,7 +710,7 @@ riff oddLimit(limit, equave = 2) {
}
const odds = popAll();
[n % d for n of odds for d of odds if gcd(n, d) == 1];
i => i rdc equave;
£ rdc equave;
sort();
}
Expand Down Expand Up @@ -775,7 +775,7 @@ riff revpose(scale = $$) {
"Change the sounding direction. Converts a descending scale to an ascending one."
$ = scale;
const equave = pop();
i => i ~% equave;
£ ~% equave;
reverse();
%equave;
return;
Expand All @@ -791,7 +791,7 @@ riff retrovert(scale = $$) {
"Retrovert the current/given scale (negative harmony i.e reflect and transpose).";
$ = scale;
const equave = pop();
i => equave %~ i;
equave %~ £;
reverse();
equave;
return;
Expand Down Expand Up @@ -831,7 +831,7 @@ riff rotate(onto = 1, scale = $$) {
const equave = $[-1];
while (--onto) equave *~ shift();
const root = shift();
i => i ~% root;
£ ~% root;
equave colorOf(root) labelOf(root);
return;
}
Expand Down Expand Up @@ -893,7 +893,7 @@ riff ground(scale = $$) {
"Use the first interval in the current/given scale as the implicit unison.";
$ = scale;
const root = shift();
i => i ~% root;
£ ~% root;
return;
}
Expand All @@ -908,7 +908,7 @@ riff elevate(scale = $$) {
$ = scale;
unshift(sanitize($[-1]~^0));
const root = sanitize(%~gcd());
i => i ~* root;
£ ~* root;
return;
}
Expand Down Expand Up @@ -940,7 +940,7 @@ riff subset(degrees, scale = $$) {
riff toHarmonics(fundamental, scale = $$) {
"Quantize the current/given scale to harmonics of the given fundamental.";
$ = scale;
i => i to~ %~fundamental colorOf(i) labelOf(i);
£ to~ %~fundamental colorOf(£) labelOf(£);
return;
}
Expand All @@ -953,7 +953,7 @@ riff harmonicsOf(fundamental, scale = $$) {
riff toSubharmonics(overtone, scale = $$) {
"Quantize the current/given scale to subharmonics of the given overtone.";
$ = scale;
i => %~(%~i to~ %~overtone) colorOf(i) labelOf(i);
%~(%~£ to~ %~overtone) colorOf(£) labelOf(£);
return;
}
Expand All @@ -969,7 +969,7 @@ riff equalize(divisions, scale = $$) {
let step = 1 \\ divisions;
if ($[-1] <> 2)
step ed= $[-1];
i => i by~ step colorOf(i) labelOf(i);
£ by~ step colorOf(£) labelOf(£);
return;
}
Expand Down Expand Up @@ -1015,7 +1015,7 @@ riff withOffset(offsets, overflow = 'drop', scale = $$) {
riff stretch(amount, scale = $$) {
"Stretch the current/given scale by the given amount. A value of \`1\` corresponds to no change.";
$ = scale;
i => i ~^ amount;
£ ~^ amount;
return;
}
Expand Down

0 comments on commit 94878d1

Please sign in to comment.