Skip to content

Commit

Permalink
More intermediate documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
frostburn committed May 1, 2024
1 parent 84e78b4 commit 98c1fe3
Showing 1 changed file with 204 additions and 20 deletions.
224 changes: 204 additions & 20 deletions documentation/intermediate-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Results in the scale `$ = [4\12, 7\12, 12\12]`.

### Coloring
If an expression evaluates to a color it is applied to all the intervals in the scale that don't have a color yet.
```javascript
```c
5/4
3/2
green
Expand All @@ -37,7 +37,7 @@ red
Results in a scale equivalent to `$ = [5/4 #008000, 3/2 #008000, 2/1 #FF0000]`.

#### Inline colors
```javascript
```c
5/4
3/2 green
2/1 red
Expand All @@ -48,7 +48,7 @@ It is up to a user interface to interprete colors. The original intent is to col

### Scale title
If an expression evaluates to a string it is used as the scale title.
```javascript
```c
"My fourth and octave"
4/3
2/1
Expand All @@ -58,7 +58,7 @@ Results in the scale `$ = [4/3, 2/1]`.
The title is included in the `.scl` export.

#### Inline labels
```javascript
```c
4/3 "My perfect fourth"
2/1 'octave'
```
Expand Down Expand Up @@ -133,6 +133,11 @@ Results in `$ = [4/3, 5/4 "my third", 3/2 "fif", 2 "octave"]`. Notice how 4/3 wa
### Boolean conversion
`true` is converted to `1` and `false` to `0` before pushing them onto the current scale.

## Ranges
Ranges of integers are generated by giving the start and end points e.g. `[1..5]` evaluates to `[1, 2, 3, 4, 5]`.

To skip over values you can specify the second element `[1,3..10]` evaluates to `[1, 3, 5, 7, 9]` (the iteration stops and rejects after reaching `11`).

## Variables
Variables in SonicWeave can hold any type of value. They must be declared before use. Variables declared `const` cannot be re-assigned while `let` variables may change what value they refer to.

Expand Down Expand Up @@ -191,6 +196,17 @@ let x, r
// r has value [2, 3, 4]
```

## Statements / line endings
Statements in SonicWeave end in a semicolon. Newlines use automatic semicolon insertion where applicable.

```c
6/5
3/2
2
```

Is actually interpreted as `6/5;3/2;2;`.

## Type system
Values in SonicWeave fall into these categories

Expand Down Expand Up @@ -350,27 +366,195 @@ Operations can be applied to intervals to create new intervals.

### Unary operators

| Name | Linear | Result | Logarithmic | Result |
| ------------- | ------- | ----------- | ----------- | ------------- |
| Identity | `+2` | `2` | `+P8` | `P8` |
| Negation | `-2` | `-2` | _N/A_ | |
| Inversion | `%2` | `1/2` | `-P8` | `P-8` |
| Inversion | `÷3/2` | `2/3` | `-P5` | `P-5` |
| Geom. inverse | _N/A_ | | `%P8` | `<1 0 0 ...]` |
| Logical NOT | `not 2` | `false` | `not P8` | `false` |
| Up | `^2` | * | `^P8` | `P8 + 1°` |
| Down | `v{2}` | * | `vP8` | `P8 - 1°` |
| Lift | `/2` | * | `/P8` | `P8 + 5°` |
| Drop | `\2` | * | `\P8` | `P8 - 5°` |
| Increment | `++i` | `3` | _N/A_ | |
| Decrement | `--i` | `1` | _N/A_ | |
| Name | Linear | Result | Logarithmic | Result |
| -------------- | --------- | ----------- | ----------- | ------------- |
| Identity | `+2` | `2` | `+P8` | `P8` |
| Negation | `-2` | `-2` | _N/A_ | |
| Inversion | `%2` | `1/2` | `-P8` | `P-8` |
| Inversion | `÷3/2` | `2/3` | `-P5` | `P-5` |
| Geom. inverse | _N/A_ | | `%P8` | `<1 0 0 ...]` |
| Logical NOT | `not 2` | `false` | `not P8` | `false` |
| Up | `^2` | * | `^P8` | `P8 + 1°` |
| Down | `v{2}` | * | `vP8` | `P8 - 1°` |
| Lift | `/2` | * | `/P8` | `P8 + 5°` |
| Drop | `\2` | * | `\P8` | `P8 - 5°` |
| Increment | `++i` | `3` | _N/A_ | |
| Decrement | `--i` | `1` | _N/A_ | |
| Absolute value | `abs(-2)` | `2` | `abs(-P8)` | `P8` |

*) If you enter `^2` it will renders as `linear([1 1>@1°.2)` (a linearized universal monzo). The operators inspired by [ups-and-downs notation](https://en.xen.wiki/w/Ups_and_downs_notation) are intended to be used with absolute pitches and relative (extended Pythagorean) intervals. These operators have no effect on the value of the operand and are only activated during [tempering](#implicit-tempering).

The down operator sometimes requires curly brackets due to `v` colliding with the Latin alphabet. Unicode `` is available but not recommended because it makes the source code harder to interprete for humans.

Drop `\` can be spelled `drop` to avoid using the backslash inside template literals. Lift `/` may be spelled `lift` for minor grammatical reasons.

#### Unary broadcasting
Note that the absolute value is technically a function call and that it behaves differently depending on the domain of the argument. In the logarithmic domain it inverts values below unity e.g. `linear(abs(logarithmic(1/2)))` evaluates to `2`.

#### Vectorized unary operators

All of the unary operators are vectorized over arrays so `-[1, 2, 3]` results in `[-1, -2, -3]`.

The only exception to this rule is `not` as it checks for emptiness of arrays. `not []` evaluates to `true` while `not [0, 1, 2]` evaluates to `false`.

The vectorized boolean NOT is called `vnot` so `vnot [0, 1, 2]` evaluates to `[true, false, false]`.

Vectorized increment/decrement creates a new copy of the affected array.

```c
let i = [1, 2];
const j = i;
++i; // Changes i to [2, 3] and pushes 2 and 3 onto the scale
j // Still [1, 2]
```

### Binary operators
There are many operators that take two operands.

#### Coalescing
| Name | Example | Result |
| ------------------ | ------------- | ------ |
| Logical AND | `2 and 0` | `0` |
| Logical OR | `0 or 2` | `2` |
| Nullish coalescing | `niente ?? 2` | `2` |

Logical operators check for *truthiness*. The falsy values are `false`, `niente`, `0`, `""` and `[]` while everything else is truthy. Note that this means that `0.0` representing zero cents or `1/1` as a linear frequency ratio is truthy.

Coalescing operators short-circuit. Execution stops once the value of the expression is known.

```c
1 and print('This executes')
0 and print("This won't execute")

false or print('This executes too')
true or print("This won't execute")

niente ?? print('This executes as well')
0 ?? print("This won't execute")
```
#### Array
| Name | Operator |
| ---------------- | --------- |
| Strict inclusion | `of` |
| Strict exclusion | `not of` |
| Inclusion | `~of` |
| Exclusion | `not ~of` |
| Key inclusion | `in` |
| Key exclusion | `not in` |
| Index inclusion | `~in` |
| Index exclusion | `not ~in` |
| Outer product | `tns` |
| Outer product | `⊗` |
Inclusion is similar to Python's `in` operator e.g. `2 of [1, 2, 3]` evaluates to `true`.
Key inclusion is similar to JavaScript's `in` operator e.g. `"foo" in {foo: 1, bar: 2}` evaluates to `true`.
Index inclusion allows for negative indices `-1 in [0]` evaluates to `false` while `-1 ~in [0]` evaluates to `true`.
Outer product a.k.a. tensoring expands all possible products in two arrays into an array of arrays e.g. `[2, 3, 5] tns [7, 11]` evaluates to
```c
[
[14, 22],
[21, 33],
[35, 55]
]
```

Beware that the product is domain-aware! Most of the time you want all possible stacks of intervals regardless of the domain. Use `~tns` to achieve this e.g. `[9/8, m2, M3] ~tns [P5, 8/7]` evaluates to
```c
[
[27/16, 9/7],
[m6, m3_7],
[M7, a4_7]
]
```

#### Vectorized logical
| Name | Operator |
| ---------------------- | -------- |
| Vectorized logical AND | `vand` |
| Vectorized logical OR | `vor` |

Binary operation is vectorized elementwise:

`[0, 1] vand [2, 3]` evaluates to `[0, 3]`.

`[0, 1] vor [2, 3]` evaluates to `[2, 1]`.

Vectorized versions of logical operators work on plain values too and do not short-circuit. `P8 white vor pop()` is a handy expression to swap out the last interval for a white-colored octave because the `pop()` command executes without further effects.

#### Boolean
| Strict equality | `===` |
| Strict inequality | `!==` |
| Equality | `==` |
| Inequality | `!=` |
| Greater than | `>` |
| Greater than or equal | `>=` |
| Less than | `<` |
| Less than or equal | `<=` |

All boolean operators vectorize over arrays. `[1, 2] === [1, 3]` evaluates to `[true, false]`.

#### Arithmetic
| Name | Linear | Result | Logarithmic | Result |
| ---------------------- | -------------- | -------- | ---------------- | ---------- |
| Addition | `3 + 5` | `8` | _N/A_ | |
| Subtraction | `5 - 3` | `2` | _N/A_ | |
| Multiplication | `2 * 3` | `6` | `P8 + P12` | `P19` |
| Multiplication | `110 Hz × 5` | `550 Hz` | `A♮2 + M17^5` | `C♯5^5` |
| Division | `6 % 2` | `3` | `P19 - P8` | `P12` |
| Division | `220 hz ÷ 2` | `110 Hz` | `A=3 - P8` | `A=2` |
| Fractions | `(1+2)/2` | `3/2` | `P12 - P8` | `P5` |
| Exponentiation | `3 ^ 2` | `9` | `P12 * 2` | `M23` |

Arithmetic operators follow *PEMDAS* order of operations (parenthesis, exponentiation, multiplication/division, addition/subtraction), but only if you use `%` or `÷` as the division operator. Fractions are so common in music that they deserve the highest precedence. The neutral third `sqrt(3/2)` may simply be spelled `3/2 ^ 1/2`. Think of fractions as being vertically stacked to get the right idea.

#### Rounding
| Name | Linear | Result | Logarithmic | Result |
| ---------------------- | -------------- | -------- | ---------------- | ---------- |
| Round (to multiple of) | `5 to 3` | `6` | _N/A_ | |
| Round (to power of) | `5 by 2` | `4` | `M17^5 to P8` | `P15` |

The linear rounding operator (`to`) measures closeness linearly `distance(x, y)` = `abs(linear(x) - linear(y))`.

The logarithmic rounding operator (`by`) measures closeness geometrically `distance(x, y)` = `abs(log(abs(logarithmic(x) - logarithmic(y))))` or more intuitively just considering the size in cents of the expression `abs(cents(x) - cents(y))`.

#### Modulo
| Name | Linear | Result | Logarithmic | Result |
| ---------------------- | -------------- | -------- | ---------------- | ---------- |
| Modulo | `5 mod 3` | `2` | _N/A_ | |
| Ceiling modulo | `0 modc 3` | `3` | _N/A_ | |
| Reduction | `5 rd 2` | `5/4` | `M17^5 mod P8` | `M3^5` |
| Ceiling reduction | `2 rdc 2` | `2` | `P8 modc P8` | `P8` |

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:
```c
M2
P4
P5
M6
P8
```

The broadcasting of `[-1..3] * P5` into `[-P5, 0 * P5, P5, 2 * P5, 3 * P5]` will be explained [below](#vector-broadcasting).

#### Extrema
TODO

#### Extended arithmetic
TODO

#### N of EDO
TODO

#### Dot product and tempering
TODO

#### Vector broadcasting
TODO

TODO: `vnot`
#### Universal operation and preference
TODO

0 comments on commit 98c1fe3

Please sign in to comment.