Skip to content

Commit

Permalink
Tweak stdlib semantics to remove past tense verbs
Browse files Browse the repository at this point in the history
Favor creating copies.
Favor popping the pre-existing scale.

ref #256
  • Loading branch information
frostburn committed Jun 3, 2024
1 parent 94878d1 commit 3c0b7f2
Show file tree
Hide file tree
Showing 14 changed files with 424 additions and 630 deletions.
218 changes: 70 additions & 148 deletions documentation/BUILTIN.md

Large diffs are not rendered by default.

21 changes: 17 additions & 4 deletions documentation/advanced-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ Ternary expressions short-circuit i.e. only the test expression and the chosen r
Functions are declared using the `riff` keyword followed by the name of the function followed by the parameters of the function.
```ocaml
riff subharmonics(start, end) {
return retroverted(start::end)
return /end::start
}
```
Above the `return` statement is suprefluous. We could've left it out and let the result unroll out of the block.
Expand All @@ -165,7 +165,7 @@ Default values for function parameters may be given using `param = value` syntax
Due to popular demand there's also the `fn` alias for function declaration.
```ocaml
fn pythagoras(up, down = 0) {
sorted([3^i rdc 2 for i of [-down..up]])
sort([3^i rdc 2 for i of [-down..up]])
}
```

Expand All @@ -174,8 +174,21 @@ Once declared, functions can be called: `subharmonics(4, 8)` evaluates to `[8/7,

while `pythagoras(4)` evaluates to `[9/8, 81/64, 3/2, 27/16, 2]`. The missing `down` argument defaulted to `0`.

#### Stblib conventions
You may have noticed that we passed an argument to `sort` in the body of `fn pythagoras`. We could've achieved the same with.
```ocaml
fn pythagoras(up, down = 0) {
[3^i rdc 2 for i of [-down..up]]
return sort()
}
```

This is because by convention built-in and standard library functions use the popped parent scale `££` (i.e. `pop$$`) as a default argument. If an argument is passed in, the pop doesn't happen and anything the riff/function produces is concatenated onto the current scale instead of replacing its contents.

Some functions like `sort` and `reverse` have in-place variants (`sortInPlace` and `reverseInplace`) that return nothing but modify the contents of the input array instead.

### Lambda expressions
Functions can be defined inline using the arrow (`=>`). e.g. `const subharmonics = ((start, end) => retroverted(start::end))`.
Functions can be defined inline using the arrow (`=>`). e.g. `const subharmonics = ((start, end) => retrovert(start::end))`.

## Throwing
To interupt execution you can throw a string message.
Expand Down Expand Up @@ -207,7 +220,7 @@ sort()
```
This first results in `$ = [3, 5, 7, 11, 13, 17]` which gets reduced to `$ = [3/2, 5/4, 7/4, 11/8, 13/8, 17/16]`. Adding the octave and sorting gives the final result `$ = [17/16, 5/4, 11/8, 3/2, 13/8, 7/4, 2]`.

Or the same with a oneliner `sorted(primes(17) rdc 2)` demonstrating the utility of broadcasting and *ceiling reduction* in a context where the unison is implicit and coincides with repeated octaves.
Or the same with a oneliner `sort(primes(17) rdc 2)` demonstrating the utility of broadcasting and *ceiling reduction* in a context where the unison is implicit and coincides with repeated octaves.

## Stdlib
SonicWeave comes with batteries included.
Expand Down
2 changes: 1 addition & 1 deletion documentation/intermediate-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ The logarithmic rounding operator (`by`) measures closeness geometrically `dista

The non-ceiling a.k.a floor variants of modulo behave as they commonly do in mathematics where `x mod x` evaluates to `0` while the ceiling variants are more useful in a musical context.

Just as the clockface starts from 12 `12 modc 12` evaluates to `12`. The fact that `P1 modc P8` evaluates to `P8` and that the unison is implicit in SonicWeave environments like Scale Workshop means that the major pentatonic scale becomes a simple oneliner `sorted([-1..3] * P5 modc P8)` evaluating to:
Just as the clockface starts from 12 `12 modc 12` evaluates to `12`. The fact that `P1 modc P8` evaluates to `P8` and that the unison is implicit in SonicWeave environments like Scale Workshop means that the major pentatonic scale becomes a simple oneliner `sort([-1..3] * P5 modc P8)` evaluating to:
```ocaml
M2
P4
Expand Down
92 changes: 66 additions & 26 deletions documentation/technical.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,32 +287,72 @@ The Basic Latin block is listed in full. Other blocks only where used.
| U+007D | } | Block end, record end |
| U+007E | ~ | Universal operator preference wing |
| U+007F | *DEL* | *N/A* |
| TODO | Â | TODO |
| TODO || TODO |
| TODO || TODO |
| TODO | × | TODO |
| TODO | ÷ | TODO |
| TODO || TODO |
| TODO || TODO |
| TODO || TODO |
| U+2228 || TODO |
| TODO | ° | TODO |
| TODO | ¢ | TODO |
| TODO || TODO |
| TODO || TODO |
| TODO | ¼ | TODO |
| TODO | ½ | TODO |
| TODO | ¾ | TODO |
| TODO | 𝄪 | TODO |
| TODO | 𝄫 | TODO |
| TODO | 𝄳 | TODO |
| TODO | 𝄲 | TODO |
| TODO || TODO |
| TODO || TODO |
| TODO || TODO |
| TODO || TODO |
| TODO | ¥ | TODO |
| TODO | (Greek lowercase) | TODO |
| U+00A2 | ¢ | Cents unit |
| U+00A3 | £ | Popped scale |
| U+00A5 | ¥ | Template argument |
| U+00B0 | ° | Edosteps unit |
| U+00B6 || Pilcrowspoob (meme) |
| U+00BC | ¼ | One-quarter accidental prefix |
| U+00BD | ½ | Semi accidental prefix, interordinal interval |
| U+00BE | ¾ | Three-quarters accidental prefix |
| U+00C2 | Â | Augmented interval |
| U+00D7 | × | Multiplication |
| U+00F7 | ÷ | Unary inversion, binary division (loose binding) |
| U+03B1 | α | Semioctave pitch alpha |
| U+03B2 | β | Semioctave pitch beta |
| U+03B3 | γ | Semioctave pitch gamma |
| U+03B4 | δ | Semioctave pitch delta |
| U+03B5 | ε | Semioctave pitch epsilon |
| U+03B6 | ζ | Semioctave pitch zeta |
| U+03B7 | η | Semioctave pitch eta |
| U+03B8 | θ | *Reserved pitch theta* |
| U+03B9 | ι | *Reserved pitch iota* |
| U+03BA | κ | *Reserved pitch kappa* |
| U+03BB | λ | *Reserved pitch lambda* |
| U+03BC | μ | *Reserved pitch mu* |
| U+03BD | ν | *Reserved pitch nu* |
| U+03BE | ξ | *Reserved pitch xi* |
| U+03BF | ο | *Reserved pitch omicron* |
| U+03C0 | π | *Reserved pitch pi* |
| U+03C1 | ρ | *Reserved pitch rho* |
| U+03C2 | ς | *Reserved pitch final sigma* |
| U+03C3 | σ | *Reserved pitch sigma* |
| U+03C4 | τ | *Reserved pitch tau* |
| U+03C5 | υ | *Reserved pitch upsilon* |
| U+03C6 | φ | Semiquartal pitch phi |
| U+03C7 | χ | Semiquartal pitch chi |
| U+03C8 | ψ | Semiquartal pitch psi |
| U+03C9 | ω | Semiquartal pitch omega |
| U+2021 || Semisharp accidental |
| U+20AC || Jorp (meme) |
| U+2150 || One-seventh accidental prefix |
| U+2151 || One-ninth accidental prefix |
| U+2152 || One-tenth accidental prefix |
| U+2153 || One-third accidental prefix |
| U+2154 || Two-thirds accidental prefix |
| U+2155 || One-fifth accidental prefix |
| U+2156 || Two-fifths accidental prefix |
| U+2157 || Three-fifths accidental prefix |
| U+2158 || Four-fifths accidental prefix |
| U+2159 || One-sixth accidental prefix |
| U+215A || Five-sixths accidental prefix |
| U+215B || One-eighth accidental prefix |
| U+215C || Three-eighths accidental prefix |
| U+215D || Five-eighths accidental prefix |
| U+215E || Seven-eighths accidental prefix |
| U+221A || Unary square root |
| U+2227 || Unary up |
| U+2228 || Unary down |
| U+226F || Sharp accidental |
| U+2295 || Lens-addition |
| U+2296 || Lens-subtraction |
| U+2297 || Tensor product |
| U+266E || Natural accidental |
| U+266D || Flat accidental |
| U+1D12A | 𝄪 | Double-sharp accidental |
| U+1D12B | 𝄫 | Double-flat accidental |
| U+1D133 | 𝄳 | Semiflat accidental |
| U+1D132 | 𝄲 | Semisharp accidental |

## Next steps

Expand Down
8 changes: 4 additions & 4 deletions examples/hexany.sw
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ for (const i in factors) {

(* Now we have a scale with combinations {3, 5, 7, 15, 21, 35} (in some order) *)

(* Make 3 the root *)
combo => combo % 3
(* Make 3 the root using implicit mapping *)
combo => combo / 3

(* Reduce each by the octave *)
combo => combo rd 2
(* Reduce each by the octave using vector broadcasting over the popped scale *)
pop$ rd 2

(* Sort in ascending order *)
sort()
Expand Down
4 changes: 2 additions & 2 deletions src/parser/__tests__/expression.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ describe('SonicWeave expression evaluator', () => {
});

it('has array comprehensions', () => {
const pyth12 = evaluate('map(str, sorted([3^i rdc 2 for i of [-4..7]]))');
const pyth12 = evaluate('map(str, sort([3^i rdc 2 for i of [-4..7]]))');
expect(pyth12).toEqual([
'2187/2048',
'9/8',
Expand Down Expand Up @@ -1742,7 +1742,7 @@ describe('SonicWeave expression evaluator', () => {
});

it('can sort an array of strings', () => {
const swac = evaluate('sorted([..."SonicWeave"])') as string[];
const swac = evaluate('sort([..."SonicWeave"])') as string[];
expect(swac.join('')).toBe('SWaceeinov');
});

Expand Down
30 changes: 26 additions & 4 deletions src/parser/__tests__/source.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ describe('SonicWeave parser', () => {
const scale = parseSource(`
const segment = [1..5];
segment;
reverse(segment);
reverseInPlace(segment);
map(i => i + 10, segment);
`);
expect(scale).toHaveLength(10);
Expand Down Expand Up @@ -1313,9 +1313,9 @@ describe('SonicWeave parser', () => {
const scale = expand(`{
riff popSort(i) {
if (isArray(i)) {
return sorted(popAll(i));
return sort(popAll(i));
}
return sorted(popAll($$));
return sort(popAll($$));
}
5:8:7:9:6:10;
Expand Down Expand Up @@ -1850,7 +1850,7 @@ describe('SonicWeave parser', () => {
5/4
3/2
2/1
sorted(%£ rdc pop$[-1])
sort(%£ rdc pop$[-1])
`);
expect(scale).toEqual(['4/3', '8/5', '2']);
});
Expand Down Expand Up @@ -1881,4 +1881,26 @@ describe('SonicWeave parser', () => {
'12\\12',
]);
});

it('can label intervals after generating', () => {
const scale = parseSource(`
4/3
3/2
2
£ ["fourth", "fifth", "octave"]
`);
expect(scale).toHaveLength(3);
expect(scale[0].label).toBe('fourth');
expect(scale[1].label).toBe('fifth');
expect(scale[2].label).toBe('octave');
});

it('can paint the whole scale', () => {
const scale = expand('3::6;white;£ "bob"');
expect(scale).toEqual([
'4/3 "bob" white',
'5/3 "bob" white',
'6/3 "bob" white',
]);
});
});
46 changes: 12 additions & 34 deletions src/parser/__tests__/stdlib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,19 +400,19 @@ describe('SonicWeave standard library', () => {
});

it('can spell the just major chord sub-overtonally', () => {
const subOvertone = parseSource('revposed(6:5:4)');
const subOvertone = parseSource('revpose(6:5:4)');
expect(subOvertone[0].totalCents()).toBeCloseTo(386.313714); // major third
expect(subOvertone[1].totalCents()).toBeCloseTo(701.955001); // perfect fifth
});

it('can spell the just major chord retroverted', () => {
const retroversion = parseSource('retroverted(10:12:15)');
const retroversion = parseSource('retrovert(10:12:15)');
expect(retroversion[0].totalCents()).toBeCloseTo(386.313714); // major third
expect(retroversion[1].totalCents()).toBeCloseTo(701.955001); // perfect fifth
});

it('can spell the just major chord reflected', () => {
const reflection = parseSource('reflected(15:12:10)');
const reflection = parseSource('reflect(15:12:10)');
expect(reflection[0].totalCents()).toBeCloseTo(386.313714); // major third
expect(reflection[1].totalCents()).toBeCloseTo(701.955001); // perfect fifth
});
Expand All @@ -438,13 +438,13 @@ describe('SonicWeave standard library', () => {
});

it('can spell the just minor chord in first inversion with root on 1/1', () => {
const firstInversion = parseSource('reflected(3:5:4)');
const firstInversion = parseSource('reflect(3:5:4)');
expect(firstInversion[0].totalCents()).toBeCloseTo(-884.358713); // major sixth
expect(firstInversion[1].totalCents()).toBeCloseTo(-498.044999); // perfect fourth
});

it('can spell the just minor chord in second inversion with root on 1/1', () => {
const secondInversion = parseSource('reflected(6:5:8)');
const secondInversion = parseSource('reflect(6:5:8)');
expect(secondInversion[0].totalCents()).toBeCloseTo(315.641287); // major sixth
expect(secondInversion[1].totalCents()).toBeCloseTo(-498.044999); // perfect fourth
});
Expand Down Expand Up @@ -498,25 +498,12 @@ describe('SonicWeave standard library', () => {
});

it('has a copying repeater', () => {
const bigScale = expand('repeated(2, 3::6)');
const bigScale = expand('repeat(2, 3::6)');
// XXX: Would be cool if that last 4/1 was 12/3, but can't come up
// with formatting rules that wouldn't mess up everything else.
expect(bigScale).toEqual(['4/3', '5/3', '6/3', '8/3', '10/3', '4/1']);
});

it('can label intervals after generating', () => {
const scale = parseSource(`
4/3
3/2
2
label(["fourth", "fifth", "octave"])
`);
expect(scale).toHaveLength(3);
expect(scale[0].label).toBe('fourth');
expect(scale[1].label).toBe('fifth');
expect(scale[2].label).toBe('octave');
});

it('preserves color upon reflection', () => {
const scale = parseSource('4/3 green;3/2;2/1 red;reflect()');
expect(scale).toHaveLength(3);
Expand Down Expand Up @@ -757,8 +744,8 @@ describe('SonicWeave standard library', () => {
const ls = labelsOf()
clear()
3::6
label(cs)
label(ls)
£ cs
£ ls
}`);
expect(scale).toEqual(['4/3 "one" red', '5/3 "two"', '6/3']);
});
Expand Down Expand Up @@ -808,8 +795,8 @@ describe('SonicWeave standard library', () => {

it('has inline labeling', () => {
const pythagoras = expand(`
labeled(['F', 'C', 'G', 'D', 'A', 'E', 'B'], [3^i rdc 2 white for i of [-2..4]])
labeled(['Gb', 'Db', 'Ab', 'Eb', 'Bb'], [3^i rdc 2 black for i of [-7..-3]])
['F', 'C', 'G', 'D', 'A', 'E', 'B'] [3^i rdc 2 white for i of [-2..4]]
['Gb', 'Db', 'Ab', 'Eb', 'Bb'] [3^i rdc 2 black for i of [-7..-3]]
sort()
`);
expect(pythagoras).toEqual([
Expand Down Expand Up @@ -920,15 +907,6 @@ describe('SonicWeave standard library', () => {
]);
});

it('can paint the whole scale', () => {
const scale = expand('3::6;white;label("bob")');
expect(scale).toEqual([
'4/3 "bob" white',
'5/3 "bob" white',
'6/3 "bob" white',
]);
});

it('can detect domains (linear)', () => {
const scale = expand(
'10/8;12/10;7/6;stack();i => simplify(i) if isLinear(i) else i'
Expand Down Expand Up @@ -1008,12 +986,12 @@ describe('SonicWeave standard library', () => {
});

it('is stacked', () => {
const scale = expand('stacked([5/4, 6/5])');
const scale = expand('stack([5/4, 6/5])');
expect(scale).toEqual(['5/4', '3/2']);
});

it("isn't that stacked actually", () => {
const scale = expand('unstacked([5/4, 3/2])');
const scale = expand('unstack([5/4, 3/2])');
expect(scale).toEqual(['5/4', '6/5']);
});

Expand Down
9 changes: 9 additions & 0 deletions src/parser/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,15 @@ export class ExpressionVisitor {
return empty;
}

if (
node.start === null &&
node.second === null &&
node.penultimate === false &&
node.end === null
) {
return typeof object === 'string' ? object : ([...object] as Interval[]);
}

let start = 0;
let step = 1;
const pu = node.penultimate;
Expand Down
Loading

0 comments on commit 3c0b7f2

Please sign in to comment.