Skip to content

Commit

Permalink
Tweak .swi format
Browse files Browse the repository at this point in the history
Swap default order of labels and colors.
Describe the .swi interchange format.

ref #236
  • Loading branch information
frostburn committed May 14, 2024
1 parent ab41036 commit 01387e2
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 42 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The `sonic-weave` package is many things.
- A [command-line interface](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/cli.md) for calculating musical quantities
- A TypeScript compatible [npm package](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/package.md)
- A [template language](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/tag.md) for running SonicWeave programs inside JavaScript
- An [interchange format](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/interchange.md) (extension `.swi`)

You may also be interested in the [technical overview](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/technical.md) of SonicWeave as a programming language.

Expand Down
2 changes: 1 addition & 1 deletion documentation/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Enneatonic 5L 4s subset of 313edo used in Sevish's track Desert Island Rain
```
### SonicWeave Interchange format
The .swi format is suitable for data interchange between programs. It preserves the internal precision of the SonicWeave runtime.
The [.swi format](https://github.com/xenharmonic-devs/sonic-weave/blob/main/documentation/interchange.md) is suitable for data interchange between programs. It preserves the internal precision of the SonicWeave runtime.
```bash
$ npx sonic-weave examples/pajara10.sw --format swi
// Created using SonicWeave 0.0.33
Expand Down
155 changes: 155 additions & 0 deletions documentation/interchange.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# SonicWeave - Technical overview
This documentation describes the .swi interchange format for transferring microtonal scales between programs

## Comments
Comments work as in JavaScript. Everything after `//` is ignored until the end of the line. Everything after `/*` is ignored until `*/` is encountered. (This includes other `/*` i.e. no nested grammar for comments.)
```c
// single line comment
/*
Comment
spanning
multiple
lines.
*/
```

## Empty lines
Empty lines and lines containing only whitespace are ignored.

## Strings
Strings are JSON encoded. They start and end with `"`. A literal `"` inside a string must be escaped with a backslash, etc..

## CSS colors
Valid CSS colors include:
- Special token `niente` indicating no color
- Named color e.g. `red` or `white`
- 4-bit hexadecimal RGB triplet e.g. `#f02`
- 8-bit hexadecimal RGB triplet e.g. `#ff01ab`
- RGB(A) literal e.g `rgba(255 50% 0 / 50%)`
- HSL(A) literal e.g. `hsl(-40deg 25% 70% / .5)`

## Scale title
The first string in the file indicates the title of the scale.
```c
"The scale title"
```

A scale title is mandatory. Untitled scales must provide an empty string.

## Unison frequency
The reference frequency is given with the syntax `1 = expr` where expressions is a valid absolute interval (described below).
```c
// Set reference frequency to 256 Hz
1 = [1 8>@Hz.2
```

Unison frequency is an optional field.

## Intervals
The label (a JSON encoded string) and the color (a CSS color or the special token `niente` for no color) are mandatory fields given in that order after an interval literal separated by spaces.

### Relative intervals
Relative interval are to be interpreted against the reference frequency if it is given.

#### 23-limit monzos
Intervals with prime factors below 29 are given as ket-vectors a.k.a. monzos starting with `[` and ending with `>`.

Below the labels indicate the meaning of each monzo:
```c
[> "1/1" niente
[-4 4 -1> "81/80" niente
[7/12> "7 sof 12" niente
[0 10/13> "10 sof 13 ed 3" #0f0
[1> "P8" white
```

#### Higher primes
Prime factors above 23 require an explicit subgroup basis given after `@` separated by periods `.`. Subgroup basis elements must be integers. No check is made to ensure that basis elements are actually prime numbers.
```c
[-1 1>@29.31 "31/29" niente
[-9 1>@2.899 "899/512" rgb(90% 80% 70% / 70%)
```

#### Non-primes
The special symbols `-1` and `0` indicate negative numbers and zero respectively. Their vector component must be `1` if present.
```c
[1>@0 "0" niente
[1 1>@-1.2 "-2" niente
```

#### Real values
Scalars that cannot be expressed as fractional monzos are given as *real* cents `rc`. The corresponding vector component is a floating-point literal. The special basis element `inf` indicates floating-point infinity. A negative unity component of `inf` indicates real zero.
```c
[-1>@inf "0r" niente
[0.>@rc "1r" niente
[1 1200.>@-1.rc "-2r" niente
[1981.7953553667824>@rc "PI" niente
[1>@inf "inf" niente
```

### Absolute pitches
The special basis element `Hz` indicates frequencies.
```c
[1 3 1 1>@Hz.2.5.11 "440 Hz" niente
[-1 2 -2>@Hz.2.5 "10ms" niente
```

A tool such as Scale Workshop normalizes durations (periods of oscillation) to frequencies, but the interchange standard support unnormalized values.

#### Real absolute pitches
The Hz exponent of real frequencies is a floating-point literal.
```c
[1. 1981.7953553667824>@Hz.rc "PI * 1Hz" niente
```

### Edosteps
To support tempering after interchanging data, the special `` basis element is used.
```c
[5>@1° "/P1" niente
```

### Not-a-number
The special token `nan` indicates a value that can't be interpreted as an interval.
```c
nan "asin(2)" niente
```

## Example
See [examples/interchange.sw](https://github.com/xenharmonic-devs/sonic-weave/blob/main/examples/interchange.sw) for various extreme values supported by the original SonicWeave runtime.

```c
// Created using SonicWeave 0.0.37

"Various values to test the .swi interchange format"

1 = [1 1 1 1>@Hz.2.3.37

[1>@0 "rational zero" black
[-1>@inf "real zero" rgb(1 1 1)
[> "rational unity" hsl(0deg 0% 100%)
[0.>@rc "real unity" #aaa
[1>@-1 "negative rational unity" niente
[1 0.>@-1.rc "negative real unity" niente
[-1 -1 -1 -1 -1 1>@2.3.5.53.5664905191661.9007199254740991 "" niente
[-13 10 0 -1> "Harrison's comma.\nIt is tempered out in \"septimal meantone\"" niente
[0 0 0 1/9007199254740991> "" niente
[0 0 0 0 9007199254740991> "" niente
[246.80000000000007>@rc "" niente
[7/12> "12-TET \"fifth\"" niente
[0 10/13> "" niente
[-4 0 0 0 0 0 0 0 1> "" niente
[-2 0 0 0 0 0 0 0 1/2> "" niente
[-4 1>@2.29 "" niente
[-2 1/2>@2.29 "" niente
[1> "rational octave" red
[1200.>@rc "real octave" #ff0000
[1981.7953553667824>@rc "pi" niente
[1. 1981.7953553667824>@Hz.rc "pi Hz" niente
[-1 -2 -2>@Hz.2.5 "" niente
[1 3 1 1>@Hz.2.5.11 "" niente
[1 1 2 1>@1°.Hz.3.37 "" niente
[-5 1 5/2 1 1>@1°.Hz.2.3.37 "" niente
[1>@inf "infinity" niente
[1 1>@-1.inf "negative infinity" niente
nan "not-a-number" niente
```
44 changes: 44 additions & 0 deletions examples/interchange.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"Various values to test the .swi interchange format"

// Need more components for that sqrt(29/16)
numComponents(20)

// MOS declaration and this comment should be ignored in .swi output
MOS 5L 2s

/*
Constants should also be ignored.
*/
const fif = P4ms
let foo = 123.4rc

C4 = 222 Hz

0 "rational zero" black
0r "real zero" rgb(1 1 1)
1 "rational unity" hsl(0deg 0% 100%)
1r "real unity" #aaa
-1 "negative rational unity"
-1r "negative real unity"
9007199254740991/9007199254740990
59049/57344 "Harrison's comma.\nIt is tempered out in \"septimal meantone\""
7^(1/9007199254740991)
11^9007199254740991
foo + foo
fif '12-TET "fifth"'
10\13<3>
23/16
sqrt(23/16)
29/16
sqrt(29/16)
2 "rational octave" red
2r "real octave" #ff0000
PI "pi"
PI * 1Hz "pi Hz"
10ms
440Hz
^G4
\gam5
inf "infinity"
-inf "negative infinity"
nan "not-a-number"
10 changes: 4 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,19 @@ export function toSonicWeaveInterchange(source: string) {
throw new Error('Missing root context.');
}
const lines = [`// Created using SonicWeave ${version}`, ''];
if (context.title) {
lines.push(JSON.stringify(context.title));
lines.push('');
}
lines.push(JSON.stringify(context.title));
lines.push('');
if (context.unisonFrequency) {
const unisonFrequency = literalToString(
context.unisonFrequency.asMonzoLiteral()
context.unisonFrequency.asInterchangeLiteral()
);
lines.push(`1 = ${unisonFrequency}`);
lines.push('');
}
for (const interval of visitor.currentScale) {
const universal = interval.shallowClone();
universal.node = universal.asMonzoLiteral(true);
let line = universal.toString(context);
let line = universal.toString(context, true);
if (line.startsWith('(') && line.endsWith(')')) {
line = line.slice(1, -1);
}
Expand Down
13 changes: 8 additions & 5 deletions src/interval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1184,19 +1184,22 @@ export class Interval {
/**
* Convert this interval to a string that faithfully represents it including color and label.
* @param context Current root context with information about root pitch and size of ups and lifts.
* @param interchange Boolean flag to always include label and color.
* @returns String that has the same value and domain as this interval if evaluated as a SonicWeave expression.
*/
toString(context?: RootContext) {
toString(context?: RootContext, interchange = false) {
const base = this.str(context);
const color = this.color ? this.color.toString() : '';
if (this.color || this.label) {
if (interchange) {
return `${base} ${JSON.stringify(this.label)} ${color || 'niente'}`;
} else if (color || this.label) {
let result = '(' + base;
if (color) {
result += ' ' + color;
}
if (this.label) {
result += ' ' + JSON.stringify(this.label);
}
if (color) {
result += ' ' + color;
}
return result + ')';
}
return base;
Expand Down
26 changes: 13 additions & 13 deletions src/parser/__tests__/source.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1354,7 +1354,7 @@ describe('SonicWeave parser', () => {
'3/2 white',
'5/3 black',
'16/9 white',
'2 white "2/1"',
'2 "2/1" white',
]);
});

Expand All @@ -1365,18 +1365,18 @@ describe('SonicWeave parser', () => {
sort()
`);
expect(scale).toEqual([
'256/243 black "A♭"',
'9/8 white "A"',
'32/27 black "B♭"',
'81/64 white "B"',
'4/3 white "C"',
'1024/729 black "D♭"',
'3/2 white "D"',
'128/81 black "E♭"',
'27/16 white "E"',
'16/9 white "F"',
'4096/2187 black "G♭"',
'2 white "G"',
'256/243 "A♭" black',
'9/8 "A" white',
'32/27 "B♭" black',
'81/64 "B" white',
'4/3 "C" white',
'1024/729 "D♭" black',
'3/2 "D" white',
'128/81 "E♭" black',
'27/16 "E" white',
'16/9 "F" white',
'4096/2187 "G♭" black',
'2 "G" white',
]);
});

Expand Down
34 changes: 17 additions & 17 deletions src/parser/__tests__/stdlib.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ describe('SonicWeave standard library', () => {
2/1 "root" blue
rotate(0)
`);
expect(scale).toEqual(['4/3 red', '3/2 "fifth"', '7/4', '2/1 blue "root"']);
expect(scale).toEqual(['4/3 red', '3/2 "fifth"', '7/4', '2/1 "root" blue']);
});

it('coalesces cents geometrically', () => {
Expand All @@ -760,7 +760,7 @@ describe('SonicWeave standard library', () => {
label(cs)
label(ls)
}`);
expect(scale).toEqual(['4/3 red "one"', '5/3 "two"', '6/3']);
expect(scale).toEqual(['4/3 "one" red', '5/3 "two"', '6/3']);
});

it('generates Raga Kafi with everything simplified', () => {
Expand Down Expand Up @@ -813,18 +813,18 @@ describe('SonicWeave standard library', () => {
sort()
`);
expect(pythagoras).toEqual([
'256/243 black "Ab"',
'9/8 white "A"',
'32/27 black "Bb"',
'81/64 white "B"',
'4/3 white "C"',
'1024/729 black "Db"',
'3/2 white "D"',
'128/81 black "Eb"',
'27/16 white "E"',
'16/9 white "F"',
'4096/2187 black "Gb"',
'2 white "G"',
'256/243 "Ab" black',
'9/8 "A" white',
'32/27 "Bb" black',
'81/64 "B" white',
'4/3 "C" white',
'1024/729 "Db" black',
'3/2 "D" white',
'128/81 "Eb" black',
'27/16 "E" white',
'16/9 "F" white',
'4096/2187 "Gb" black',
'2 "G" white',
]);
});

Expand Down Expand Up @@ -923,9 +923,9 @@ describe('SonicWeave standard library', () => {
it('can paint the whole scale', () => {
const scale = expand('3::6;white;label("bob")');
expect(scale).toEqual([
'4/3 white "bob"',
'5/3 white "bob"',
'6/3 white "bob"',
'4/3 "bob" white',
'5/3 "bob" white',
'6/3 "bob" white',
]);
});

Expand Down

0 comments on commit 01387e2

Please sign in to comment.