-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement methods for obtaining TE optimal tuning maps
ref #365
- Loading branch information
Showing
14 changed files
with
564 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -513,6 +513,116 @@ Intrinsic behavior may be evoked explicitly by simply calling a value e.g. `3/2( | |
Some expressions like `440Hz` or `440 Hz` appear similar to intrinsic calls and would correspond to `440 × (1 Hz)` but `600.0 Hz` is actually `600.0e × (1 Hz)`. | ||
It's legal to declare `let Hz = 'whatever'`, but the grammar prevents the `Hz` variable from invoking intrinsic behavior of integer literals from the right. | ||
|
||
## Advanced tempering | ||
While vals are technically powerful enough to describe any pure-octaves tuning to sufficient precision, it can be challenging to discover them from first principles. | ||
|
||
### Tempering out a comma | ||
[Tenney-Euclidean tuning](https://en.xen.wiki/w/Tenney-Euclidean_tuning) is a commonly used scheme for adjusting intervals in such a way that some of them coincide. This can help tame the complexity of pure just intonation. It is desirable to make these tiny adjustments with minimal damage. | ||
|
||
To make two intervals such as `9/8` and `10/9` coincide we *temper out* their geometric difference `81/80` using the `TE` helper. | ||
```ocaml | ||
(* Generate Pythagorean major scale *) | ||
rank2(3/2, 5, 1) | ||
(* Temper out the syntonic comma 81/80 *) | ||
TE(9/8 ÷ 10/9) | ||
``` | ||
|
||
Now the `81/64` major thirds sounds more like `5/4` and the major chord resembles `4:5:6`. Compared to just intonation the scale maintains the nice property that there are only two step sizes. | ||
|
||
The downside is that the scale is now expressed in terms of real cents and all structural information such as the stack of pure fifths has been lost. | ||
|
||
#### Maintaining pure octaves | ||
TE optimal tunings damage all primes including the octave which is often undesirable. | ||
|
||
To destretch the octave after the fact use the stretching operator `~^` alongside the size-comparison operator `~/_`. The expression `2 ~/_ $[-1]` tells you how much wider the octave is compared to the last interval in the scale (the current equave). | ||
|
||
```ocaml | ||
"POTE meantone[7]" | ||
rank2(3/2, 5, 1) | ||
TE(81/80) | ||
(* Destretch octaves *) | ||
£ ~^ (2 ~/_ £[-1]) | ||
``` | ||
|
||
##### CTE | ||
[Constrained tunings](https://en.xen.wiki/w/Constrained_tuning) have more finesse when it comes to maintaining the size of important intervals like the octave. While SonicWeave doesn't support CTE exactly, you can get close enough by assigning a large weight to the octave. | ||
|
||
```ocaml | ||
"Near-CTE meantone[7]" | ||
rank2(3/2, 5, 1) | ||
(* Temper out the syntonic comma while making octaves 1000 times more important than anything else. *) | ||
TE(81/80, 1000) | ||
``` | ||
|
||
#### Tempering out multiple commas | ||
Multiple commas can be tempered out by passing in an array to `TE`. Multiple primes can be weighted by passing in an array as the second argument. | ||
|
||
```ocaml | ||
"Unimarv[19] with a focus on near-pure octaves and undecimal harmony" | ||
(* Unimarv[19] 5-limit transversal *) | ||
25/24 | ||
16/15 | ||
9/8 | ||
75/64 | ||
6/5 | ||
5/4 | ||
32/25 | ||
4/3 | ||
45/32 | ||
64/45 | ||
3/2 | ||
25/16 | ||
8/5 | ||
5/3 | ||
128/75 | ||
16/9 | ||
15/8 | ||
48/25 | ||
2/1 | ||
(* Temper out the marvel comma and the keenanisma *) | ||
TE( | ||
[225/224, 385/384], | ||
(* Give 1000 times more weight to octaves and 10 times more weight to prime 11. *) | ||
[1000, 1, 1, 1, 10], | ||
) | ||
``` | ||
|
||
### Combining multiple vals | ||
Linear combinations of vals share the same temperament. In the 11-limit unimarv can also be expressed as the 19 & 22 temperament. In SonicWeave we must be explicit about the subgroup `@2.3.5.7.11` or `@.11` for short and pass the vals as arguments to `TE`. | ||
|
||
```ocaml | ||
"Marveldene but using unimarv for no reason" | ||
(* Generate duodene *) | ||
eulerGenus(675) | ||
(* TE unimarv 19 & 22 *) | ||
TE([[email protected], [email protected]]) | ||
``` | ||
|
||
It is strongly advised to use an explicit subgroup when combining vals in `TE`. Depending on the runtime the ambient subgroup might contain tens of primes, most of little interest, only wasting compute and hurting the low prime accuracy of the tuning. | ||
|
||
#### Subgroup weights | ||
Because vals always come with a subgroup basis the weights are associated with it instead of the actual primes when combining vals. | ||
```ocaml | ||
"Barbados[5]" | ||
15/13 | ||
4/3 | ||
3/2 | ||
26/15 | ||
2/1 | ||
(** Importance weights: | ||
* 2/1: 200% | ||
* 3/1: 100% | ||
* 13/5: 300% | ||
*) | ||
TE([[email protected]/5, [email protected]/5], [2, 1, 3]) | ||
``` | ||
|
||
### Obscure types | ||
| Type | Literal | Meaning | | ||
| ----------------- | ---------- | ---------------------------------------------------------- | | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
|
||
"Generator sequence 4:5:6 around the half-octave (Jubilismic TE)" | ||
gs(geodiff(4:5:6), 10, 1\2, 2); | ||
PrimeMapping(1199.35339, 1901.955, 2779.34146, 3379.01816); | ||
(* Temper out the jubilisma 50/49 using a Tenney-Euclid optimal tuning. *) | ||
TE([50/49]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
"The Marveldene, a 2.3.5.7 classic. Septimal Aeolian minor oriented." | ||
eulerGenus(675, 15) | ||
PrimeMapping(1200.5978, 1901.3543, 2785.0245, 3369.7682) | ||
(* Temper out 225/224 by combining 3 linearly independent vals that support the marvel temperament. *) | ||
TE([12@.7, 31@.7, 41@.7]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,7 @@ | ||
"(A)GS(7/6, 8/7) over 2^1/2 (Semitonismic TE)" | ||
"(A)GS(7/6, 8/7) over 2^1/2 (Semitonismic POTE)" | ||
|
||
csgs([7/6, 8/7], 3, 2^1/2, 2) | ||
PrimeMapping( | ||
1200.29451, | ||
1902.25094, | ||
niente, | ||
niente, | ||
niente, | ||
niente, | ||
4902.98721 | ||
) | ||
(* Temper out the semitonisma. TE optimal. *) | ||
TE([289/288]) | ||
(* De-stretch the octave. (Pure Octaves TE) *) | ||
£ ~^ (2 ~/_ £[-1]) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
import {describe, expect, it} from 'vitest'; | ||
import {TuningMap, combineTuningMaps, vanishCommas} from '../temper'; | ||
import {LOG_PRIMES, applyWeights, dot, unapplyWeights} from 'xen-dev-utils'; | ||
|
||
describe('Val combination optimizer', () => { | ||
it('computes TE meantone', () => { | ||
const p12: TuningMap = [12, 19, 28].map(c => (c / 12) * LOG_PRIMES[0]); | ||
const p19: TuningMap = [19, 30, 44].map(c => (c / 19) * LOG_PRIMES[0]); | ||
const meantone = combineTuningMaps( | ||
[1, 1, 1], | ||
[unapplyWeights(p12, LOG_PRIMES), unapplyWeights(p19, LOG_PRIMES)] | ||
); | ||
const map = applyWeights(meantone, LOG_PRIMES); | ||
expect(Math.abs(dot(map, [-4, 4, -1]))).toBeCloseTo(0, 9); | ||
expect(dot(map, [1, 0, 0])).toBeCloseTo(LOG_PRIMES[0], 2); | ||
expect(dot(map, [-1, 1, 0])).toBeCloseTo(LOG_PRIMES[1] - LOG_PRIMES[0], 2); | ||
expect(dot(map, [0, 0, 1])).toBeCloseTo(LOG_PRIMES[2], 2); | ||
expect((((map[1] - map[0]) / map[0]) * 1200).toFixed(3)).toBe('696.239'); | ||
expect(map.map(c => ((c / LOG_PRIMES[0]) * 1200).toFixed(3))).toEqual([ | ||
'1201.397', | ||
'1898.446', | ||
'2788.196', | ||
]); | ||
}); | ||
|
||
it('almost computes CTE meantone', () => { | ||
const p5 = [5, 8, 12]; | ||
const p7 = [7, 11, 16]; | ||
const jip = LOG_PRIMES.slice(0, 3); | ||
const weights = LOG_PRIMES.slice(0, 3); | ||
weights[0] *= 0.005; | ||
const meantone = combineTuningMaps(unapplyWeights(jip, weights), [ | ||
unapplyWeights(p5, weights), | ||
unapplyWeights(p7, weights), | ||
]); | ||
const map = applyWeights(meantone, weights); | ||
expect(Math.abs(dot(map, [-4, 4, -1]))).toBeCloseTo(0, 7); | ||
expect(dot(map, [1, 0, 0])).toBeCloseTo(LOG_PRIMES[0], 7); | ||
expect(dot(map, [-1, 1, 0])).toBeCloseTo(LOG_PRIMES[1] - LOG_PRIMES[0], 2); | ||
expect(dot(map, [0, 0, 1])).toBeCloseTo(LOG_PRIMES[2], 2); | ||
expect((((map[1] - map[0]) / map[0]) * 1200).toFixed(4)).toBe('697.2143'); | ||
expect(map.map(c => ((c / LOG_PRIMES[0]) * 1200).toFixed(4))).toEqual([ | ||
'1200.0000', | ||
'1897.2144', | ||
'2788.8572', | ||
]); | ||
}); | ||
}); | ||
|
||
describe('Comma vanisher', () => { | ||
it('computes TE meantone', () => { | ||
const syntonic = [-4, 4, -1]; | ||
const meantone = vanishCommas( | ||
[1, 1, 1], | ||
[applyWeights(syntonic, LOG_PRIMES)] | ||
); | ||
const map = applyWeights(meantone, LOG_PRIMES); | ||
expect(Math.abs(dot(map, [-4, 4, -1]))).toBeCloseTo(0, 9); | ||
expect(dot(map, [1, 0, 0])).toBeCloseTo(LOG_PRIMES[0], 2); | ||
expect(dot(map, [-1, 1, 0])).toBeCloseTo(LOG_PRIMES[1] - LOG_PRIMES[0], 2); | ||
expect(dot(map, [0, 0, 1])).toBeCloseTo(LOG_PRIMES[2], 2); | ||
expect((((map[1] - map[0]) / map[0]) * 1200).toFixed(3)).toBe('696.239'); | ||
expect(map.map(c => ((c / LOG_PRIMES[0]) * 1200).toFixed(3))).toEqual([ | ||
'1201.397', | ||
'1898.446', | ||
'2788.196', | ||
]); | ||
}); | ||
|
||
it('almost computes CTE meantone', () => { | ||
const syntonic = [-4, 4, -1]; | ||
const jip = LOG_PRIMES.slice(0, 3); | ||
const weights = LOG_PRIMES.slice(0, 3); | ||
weights[0] *= 0.005; | ||
const meantone = vanishCommas(unapplyWeights(jip, weights), [ | ||
applyWeights(syntonic, weights), | ||
]); | ||
const map = applyWeights(meantone, weights); | ||
expect(Math.abs(dot(map, [-4, 4, -1]))).toBeCloseTo(0, 7); | ||
expect(dot(map, [1, 0, 0])).toBeCloseTo(LOG_PRIMES[0], 7); | ||
expect(dot(map, [-1, 1, 0])).toBeCloseTo(LOG_PRIMES[1] - LOG_PRIMES[0], 2); | ||
expect(dot(map, [0, 0, 1])).toBeCloseTo(LOG_PRIMES[2], 2); | ||
expect((((map[1] - map[0]) / map[0]) * 1200).toFixed(4)).toBe('697.2143'); | ||
expect(map.map(c => ((c / LOG_PRIMES[0]) * 1200).toFixed(4))).toEqual([ | ||
'1200.0000', | ||
'1897.2144', | ||
'2788.8572', | ||
]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.