# Scale constructions


# Harmonic series segments

A harmonic series segment is a range of consecutive numbers divided by the
smallest one. For example, the harmonic series segment between 6 and 12 is

```
6/6, 7/6, 8/6, 9/6, 10/6, 11/6, 12/6
```

or simplifying the fractions

```
1/1, 7/6, 4/3, 3/2, 5/3, 11/6, 2/1
```

If the largest number is double the smallest, the scale will span an octave.

## Subharmonic series segment

If we take the reciprocals of a range of consecutive numbers and multiply by
the largest one, we get a subharmonic series segment. For example, the
subharmonic series segment from the range 6 to 12 is

```
12/6, 12/7, 12/8, 12/9, 12/10, 12/11, 12/12
```

or simplifying fractions and sorting

```
1/1, 12/11, 6/5, 4/3, 3/2, 12/7, 2/1
```

## Further reading

- Harry Partch, Genesis of a Music, 2nd ed. Da Capo Press (1979)
- Augusto Novaro, [Sistema Natural de la Música](https://www.anaphoria.com/novaro51.pdf), pp. 36–37, (1951)

## Python code

```python
from fractions import Fraction


def harmonic_series_segment(m, n):
    """
    >>> harmonic_series_segment(6, 12)
    [Fraction(1, 1), Fraction(7, 6), Fraction(4, 3), Fraction(3, 2), Fraction(5, 3), Fraction(11, 6), Fraction(2, 1)]
    """
    return [Fraction(i, m) for i in range(m, n + 1)]


def subharmonic_series_segment(m, n):
    """
    >>> subharmonic_series_segment(6, 12)
    [Fraction(1, 1), Fraction(12, 11), Fraction(6, 5), Fraction(4, 3), Fraction(3, 2), Fraction(12, 7), Fraction(2, 1)]
    """
    return sorted(Fraction(n, i) for i in range(m, n + 1))
```


---


# Diamonds

Given a list of numbers, say [1, 3, 5, 7], find all ratios

```
    1       3       5       7

1   1/1     3/1     5/1     7/1

3   1/3     3/3     5/3     7/3

5   1/5     3/5     5/5     7/5

7   1/7     3/7     5/7     7/7
```

and reduce within one octave

```
    1       3       5       7

1   1/1     3/2     5/4     7/4

3   4/3     1/1     5/3     7/6

5   8/5     6/5     1/1     7/5

7   8/7    12/7    10/7     1/1
```

The unique ratios 1/1, 8/7, 7/6, 6/5, 5/4, 4/3, 7/5, 10/7, 3/2, 8/5, 5/3, 12/7,
7/4 form the [1, 3, 5, 7] diamond.

## Further reading

- Harry Partch, Genesis of a Music, 2nd ed. Da Capo Press (1979)
- Augusto Novaro, [Sistema Natural de la Música](https://www.anaphoria.com/novaro51.pdf), p. 46, (1951)

## Python code

```python
from fractions import Fraction
from math import log2, floor


def reduce(x):
    return x * Fraction(2) ** (-floor(log2(x)))


def diamond(A):
    """
    >>> diamond([1, 3, 5])
    [Fraction(1, 1), Fraction(6, 5), Fraction(5, 4), Fraction(4, 3), Fraction(3, 2), Fraction(8, 5), Fraction(5, 3)]
    """
    return sorted(set(reduce(Fraction(x, y)) for x in A for y in A))
```


---


# Combination product sets

Given the list of numbers [1, 3, 5, 7], take all products of two numbers,

```
1*3, 1*5, 1*7, 3*5, 3*7, 5*7
```

Now pick one of these products as the root, say 3. Divide each number by the
root

```
1, 5/3, 7/3, 5, 7, 35/3
```

and reduce within one octave

```
1, 5/3, 7/6, 5/4, 7/4, 35/24
```

These ratios 1, 7/6, 5/4, 35/24, 5/3, 7/4 form the 1-3-5-7 hexany.

Choosing different products as root gives different modes of the same scale.

In general, given a list of n numbers, taking products of k numbers gives a CPS
written nCk. The scale gets a name based on its number of tones:

| Name          | Tones |
|---------------|-------|
| monany        | 1     |
| dyany         | 2     |
| triany        | 3     |
| tetrany       | 4     |
| pentany       | 5     |
| hexany        | 6     |
| dekany        | 10    |
| pentadekany   | 15    |
| eikosany      | 20    |

## Further reading

- [The Hexany, the Eikosany, and the Other Combination Product Sets](https://www.anaphoria.com/wilsoncps.html), The Wilson Archives

## Python code

```python
from itertools import combinations
from fractions import Fraction
from math import floor, log2, prod


def reduce(x):
    return x * Fraction(2) ** (-floor(log2(x)))


def cps(A, k, *, root=None):
    """
    >>> cps([1, 3, 5, 7], 2)
    [Fraction(1, 1), Fraction(7, 6), Fraction(5, 4), Fraction(35, 24), Fraction(5, 3), Fraction(7, 4)]
    """
    products = [Fraction(prod(a)) for a in combinations(A, k)]
    if root is None:
        root = products[0]
    return sorted(reduce(x / root) for x in products)
```


---


# Euler-Fokker genera

Take a list of numbers, say [3, 3, 5]. Form all products of any number of terms,

```
1, 3, 5, 3*3, 3*5, 3*3*5
```

where 1 comes from 'the product of no terms'. Octave reduce to give

```
1, 3/2, 5/4, 9/8, 15/8, 45/32
```

and sort to give the scale

```
1, 9/8, 5/4, 45/32, 3/2, 15/8
```

with period 2/1.

The Euler-Fokker genus is made up of the [combination product
sets](/constructions/cps/) for each number of terms.

## Further reading

- [What is an Euler-Fokker genus?](https://www.huygens-fokker.org/microtonality/efg.html), Huygens-Fokker Foundation

## Python code

```python
"""
>>> numbers = [3, 3, 5]
>>> all_cps = []
>>> for k in range(len(numbers) + 1):
...     root, scale = cps_with_root(numbers, k)
...     shifted_scale = sorted(reduce(x * reduce(root)) for x in scale)
...     print(shifted_scale)
...     all_cps.extend(shifted_scale)
[Fraction(1, 1)]
[Fraction(5, 4), Fraction(3, 2)]
[Fraction(9, 8), Fraction(15, 8)]
[Fraction(45, 32)]
>>> print(euler_fokker_genus(numbers))
[Fraction(1, 1), Fraction(9, 8), Fraction(5, 4), Fraction(45, 32), Fraction(3, 2), Fraction(15, 8)]
>>> assert sorted(all_cps) == euler_fokker_genus(numbers)
"""

from itertools import combinations
from fractions import Fraction
from math import prod, log2, floor


def reduce(x):
    return x * Fraction(2) ** (-floor(log2(x)))


def euler_fokker_genus(numbers):
    """
    >>> euler_fokker_genus([3, 3, 5])
    [Fraction(1, 1), Fraction(9, 8), Fraction(5, 4), Fraction(45, 32), Fraction(3, 2), Fraction(15, 8)]
    """
    products = [
        prod(a) for k in range(len(numbers) + 1) for a in combinations(numbers, k)
    ]
    return sorted({reduce(x) for x in products})


def cps_with_root(numbers, k):
    products = [Fraction(prod(a)) for a in combinations(numbers, k)]
    root = products[0]
    return root, sorted({reduce(x / root) for x in products})
```


---


# Tritriadic scales

Take the triad

```
1, 5/4, 3/2
```

Make one copy starting on 3/2

```
3/2, 15/8, 9/4
```

and one copy starting on 4/3, the octave complement of 3/2, giving

```
4/3, 5/3, 2/1
```

Combining these three triads and octave-reducing each note gives the seven-note
scale

```
1, 9/8, 5/4, 4/3, 3/2, 5/3, 15/8
```

which is the tritriadic scale built from 1, 5/4, 3/2.

In general, for a triad 1, M, D, the tritriadic scale consists of the notes

```
1, M, D, D*M, D*D, 1/D, M/D
```

octave reduced and sorted.

**M->T variant**: triads placed on M and its octave complement 1/M:

```
1, M, D, M*M, D*M, 1/M, D/M
```

**D->M variant**: triads placed on D/M and its octave complement M/D:

```
1, M, D, D/M, D*D/M, M/D, M*M/D
```

## Further reading

- John Chalmers, [Tritriadic Scales with Seven Tones](https://www.xenharmonikon.org/2024/06/17/tritriadic-scales-with-seven-tones/), Xenharmonikon Online

## Python code

```python
from fractions import Fraction
from math import floor, log2


def reduce(x):
    return x * Fraction(2) ** (-floor(log2(x)))


def tritriadic(M, D):
    """
    >>> tritriadic(Fraction(5, 4), Fraction(3, 2))
    [Fraction(1, 1), Fraction(9, 8), Fraction(5, 4), Fraction(4, 3), Fraction(3, 2), Fraction(5, 3), Fraction(15, 8)]
    """
    triad1 = [1, M, D]
    triad2 = [x * D for x in triad1]
    triad3 = [x / D for x in triad1]
    return sorted(set(reduce(x) for x in triad1 + triad2 + triad3))


def tritriadic_mt(M, D):
    """
    >>> tritriadic_mt(Fraction(5, 4), Fraction(3, 2))
    [Fraction(1, 1), Fraction(6, 5), Fraction(5, 4), Fraction(3, 2), Fraction(25, 16), Fraction(8, 5), Fraction(15, 8)]
    """
    triad1 = [1, M, D]
    triad2 = [x * M for x in triad1]
    triad3 = [x / M for x in triad1]
    return sorted(set(reduce(x) for x in triad1 + triad2 + triad3))


def tritriadic_dm(M, D):
    """
    >>> tritriadic_dm(Fraction(5, 4), Fraction(3, 2))
    [Fraction(1, 1), Fraction(25, 24), Fraction(6, 5), Fraction(5, 4), Fraction(3, 2), Fraction(5, 3), Fraction(9, 5)]
    """
    triad1 = [1, M, D]
    triad2 = [x * D / M for x in triad1]
    triad3 = [x * M / D for x in triad1]
    return sorted(set(reduce(x) for x in triad1 + triad2 + triad3))
```


---


# Triadic diamonds

Given a triad 1/1, M, D, a triadic diamond is the seven notes

```
1/1, M, D, 2/M, 2/D, D/M, M/D
```

octave reduced and sorted. For example, for M = 5/4, D = 3/2, we get

```
1/1, 5/4, 3/2, 8/5, 4/3, 6/5, 5/3
```

giving the sorted scale

```
1/1, 6/5, 5/4, 4/3, 3/2, 8/5, 5/3
```

On a lattice where a factor of M moves one step up, a factor of D one step to
the right, the triadic diamond appears as

```
M/D       M

2/D       1/1       D

          2/M       D/M
```

A triadic diamond is the [diamond](/constructions/diamonds/) construction
applied to the triad 1, M, D.

## Triadic reversed diamonds

A triadic reversed diamond is the seven notes

```
1/1, M, D, 2/M, 2/D, D*M, 2/(D*M)
```

octave reduced and sorted. On the lattice these notes are

```
          M         D*M

2/D       1/1       D

2/(D*M)   2/M
```

For M = 5/4, D = 3/2 this gives the notes

```
1/1, 5/4, 3/2, 8/5, 4/3, 15/8, 16/15
```

and sorting gives the scale

```
1/1, 16/15, 5/4, 4/3, 3/2, 8/5, 15/8
```

## Further reading

- John H. Chalmers, Jr., The Triadic Diamond, the Triadic Reversed Diamond, and their Constituent Tetrachords when D=3/2, Xenharmonikon 15 (1993), p.64

## Python code

```python
from fractions import Fraction
from math import floor, log2


def reduce(x):
    return x * Fraction(2) ** (-floor(log2(x)))


def triadic_diamond(M, D):
    """
    >>> triadic_diamond(Fraction(5, 4), Fraction(3, 2))
    [Fraction(1, 1), Fraction(6, 5), Fraction(5, 4), Fraction(4, 3), Fraction(3, 2), Fraction(8, 5), Fraction(5, 3)]
    """
    notes = [1, M, D, 2 / M, 2 / D, D / M, M / D]
    return sorted(reduce(x) for x in notes)


def triadic_reversed_diamond(M, D):
    """
    >>> triadic_reversed_diamond(Fraction(5, 4), Fraction(3, 2))
    [Fraction(1, 1), Fraction(16, 15), Fraction(5, 4), Fraction(4, 3), Fraction(3, 2), Fraction(8, 5), Fraction(15, 8)]
    """
    notes = [1, M, D, 2 / M, 2 / D, D * M, 2 / (D * M)]
    return sorted(reduce(x) for x in notes)
```


---


# Tetrachordal scales

A tetrachord is four notes spanning a fourth. For example, Archytas' diatonic
tetrachord is

```
1/1, 28/27, 32/27, 4/3
```

with steps

```
28/27, 8/7, 9/8
```

A tetrachordal scale, in its simplest form, is two tetrachords separated by a
9/8 whole tone. For example, combining a lower tetrachord with steps

```
9/8, 28/27, 8/7
```

with an upper tetrachord with steps

```
28/27, 9/8, 8/7
```

both being permutations of Archytas' diatonic tetrachord, gives the steps

```
9/8, 28/27, 8/7, 9/8, 28/27, 9/8, 8/7
|----lower----|       |----upper----|
```

and so the scale

```
1/1, 9/8, 7/6, 4/3, 3/2, 14/9, 7/4, 2/1
```

## Further reading

- John Chalmers, [Divisions of the Tetrachord](https://eamusic.dartmouth.edu/~larry/published_articles/divisions_of_the_tetrachord/index.html),
  Frog Peak Music (1993)
- [MaqamWorld — Jins](https://www.maqamworld.com/en/jins.php)

## Python code

```python
import math
from fractions import Fraction

F = Fraction


def tetrachordal(t1, t2, *, disjunct=True):
    """
    >>> t1 = (Fraction(9, 8), Fraction(28, 27), Fraction(8, 7))
    >>> t2 = (Fraction(28, 27), Fraction(9, 8), Fraction(8, 7))
    >>> tetrachordal(t1, t2)
    [Fraction(1, 1), Fraction(9, 8), Fraction(7, 6), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(7, 4), Fraction(2, 1)]
    >>> tetrachordal(t1, t2, disjunct=False)
    [Fraction(1, 1), Fraction(9, 8), Fraction(7, 6), Fraction(4, 3), Fraction(112, 81), Fraction(14, 9), Fraction(16, 9)]
    """
    for t in (t1, t2):
        assert len(t) == 3
        assert math.prod(t) == Fraction(4, 3)

    if disjunct:
        steps = [*t1, Fraction(9, 8), *t2]
    else:
        steps = [*t1, *t2]

    result = [Fraction(1, 1)]
    for step in steps:
        result.append(result[-1] * step)

    assert result[0] == Fraction(1, 1)
    assert result[-1] == (Fraction(2, 1) if disjunct else Fraction(16, 9))

    return result
```


---


# Marwa permutations

Any seven-note scale can be built by stacking 'variable-sized fourths' found by
stepping through the scale taking every third note (in a seven-note scale a
fourth spans three scale steps). For example, taking the scale

```
1/1, 9/8, 7/6, 4/3, 3/2, 14/9, 7/4, 2/1
```

stepping through by three steps, wrapping at the octave

```
4/3  /  1/1   ->  4/3
7/4  /  4/3   ->  21/16
7/6  /  7/4   ->  4/3
14/9 /  7/6   ->  4/3
9/8  /  14/9  ->  81/56
3/2  /  9/8   ->  4/3
2/1  /  3/2   ->  4/3
```

we get the fourths


```
4/3, 21/16, 4/3, 4/3, 81/56, 4/3, 4/3
```

Stacking these fourths gives

```
                  1/1
1/1  *  4/3   ->  4/3
4/3  *  21/16 ->  7/4
7/4  *  4/3   ->  7/6
7/6  *  4/3   ->  14/9
14/9 *  81/56 ->  9/8
9/8  *  4/3   ->  3/2
3/2  *  4/3   ->  2/1
```

reproducing our original scale.

By stacking the same fourths in a different order, we get scales related to our
original scale; these are called Marwa permutations. For example, permuting the
fourths to give

```
4/3, 21/16, 4/3, 81/56, 4/3, 4/3, 4/3
```

and stacking gives the scale

```
1/1, 9/8, 7/6, 4/3, 3/2, 27/16, 7/4, 2/1
```

different from the original scale or any of its modes.

As starting scales, Wilson takes tetrachordal scales built from two of the same
tetrachord. For example, stacking the steps of Archytas' diatonic tetrachord (28/27,
8/7, 9/8), a whole tone 9/8, and those steps again gives the scale

```
1/1, 28/27, 32/27, 4/3, 3/2, 14/9, 16/9, 2/1
```

which is built from fourths

```
4/3, 4/3, 4/3, 21/16, 4/3, 81/56, 4/3
```

Our example scale fourths are a permutation of these.

Wilson has a specific scheme for which permutations to consider, perhaps best
appreciated by looking at the diagrams in the Marwa Permutations paper and
implemented in the code below.

## Further reading

- Erv Wilson, [The Marwa Permutations](https://www.anaphoria.com/xen9mar.pdf),
  Xenharmonikon 9 (1986)
- John Chalmers, [Divisions of the Tetrachord](https://eamusic.dartmouth.edu/~larry/published_articles/divisions_of_the_tetrachord/index.html),
  Frog Peak Music (1993), Chapter 6, p. 110

## Python code

```python
"""
>>> from collections import Counter
>>> ts = tetrachordal_scale(Fraction(28, 27), Fraction(8, 7), Fraction(9, 8))
>>> tetrachordal_fourths = step_through(ts, 3)
>>> fourths = [Fraction(21, 16), Fraction(81, 56)] + 5 * [Fraction(4, 3)]
>>> assert Counter(fourths) == Counter(tetrachordal_fourths)
>>> marwa(fourths)[13]
[Fraction(1, 1), Fraction(28, 27), Fraction(32, 27), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(16, 9), Fraction(2, 1)]
>>> assert marwa(fourths)[13] == ts
>>> marwa(fourths)[7]
[Fraction(1, 1), Fraction(9, 8), Fraction(7, 6), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(7, 4), Fraction(2, 1)]
>>> marwa(fourths)[6]
[Fraction(1, 1), Fraction(9, 8), Fraction(7, 6), Fraction(4, 3), Fraction(3, 2), Fraction(27, 16), Fraction(7, 4), Fraction(2, 1)]
"""

from fractions import Fraction
from math import floor, log2

F = Fraction


def tetrachordal_scale(a, b, c):
    """
    >>> tetrachordal_scale(Fraction(28, 27), Fraction(8, 7), Fraction(9, 8))
    [Fraction(1, 1), Fraction(28, 27), Fraction(32, 27), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(16, 9), Fraction(2, 1)]
    """
    steps = [a, b, c]
    return stack(steps + [Fraction(9, 8)] + steps)


def octave_reduce(x):
    if x == 2:
        return x
    return x * Fraction(2) ** (-floor(log2(x)))


def step_through(scale, step):
    """
    >>> step_through([Fraction(1, 1), Fraction(9, 8), Fraction(7, 6), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(7, 4), Fraction(2, 1)], 3)
    [Fraction(4, 3), Fraction(21, 16), Fraction(4, 3), Fraction(4, 3), Fraction(81, 56), Fraction(4, 3), Fraction(4, 3)]
    """
    N = len(scale) - 1
    i = 0
    result = []
    while len(result) < N:
        j = (i + step) % N
        result.append(octave_reduce(scale[j] / scale[i]))
        i = j
    return result


def stack(fs):
    """
    >>> stack([Fraction(4, 3), Fraction(21, 16), Fraction(4, 3), Fraction(4, 3), Fraction(81, 56), Fraction(4, 3), Fraction(4, 3)])
    [Fraction(1, 1), Fraction(9, 8), Fraction(7, 6), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(7, 4), Fraction(2, 1)]
    """
    x = Fraction(1)
    notes = [Fraction(1)]
    for f in fs:
        x = octave_reduce(x * f)
        notes.append(x)
    return sorted(notes)


def marwa_permute(fourths):
    """
    Permute a list of fourths in the way Wilson does in the Marwa permutations paper.

    The 7th interval (fourths[-1]) is always fixed; only the first six are
    permuted. Special intervals are those not equal to 4/3. Their positions in the
    starting configuration (special_positions) determine the minimum gap enforced
    between them in each permutation — matching Wilson's exact enumeration order in
    the figures. When specials are adjacent in the starting config
    (special_positions=[0,1] or [0,1,2]), this reduces to all C(6, num_specials)
    combinations maintaining relative order. When they start with a gap of 2
    (special_positions=[0,2], figures 11–18), 5 adjacent placements are excluded,
    giving 10 rather than 15 permutations. This gap constraint was
    reverse-engineered to match Wilson's enumeration in the figures.

    >>> for x in marwa_permute([Fraction(729, 512)] + 6 * [Fraction(4, 3)]): print(', '.join(map(str, x)))
    729/512, 4/3, 4/3, 4/3, 4/3, 4/3, 4/3
    4/3, 729/512, 4/3, 4/3, 4/3, 4/3, 4/3
    4/3, 4/3, 729/512, 4/3, 4/3, 4/3, 4/3
    4/3, 4/3, 4/3, 729/512, 4/3, 4/3, 4/3
    4/3, 4/3, 4/3, 4/3, 729/512, 4/3, 4/3
    4/3, 4/3, 4/3, 4/3, 4/3, 729/512, 4/3
    >>>
    >>> for x in marwa_permute([Fraction(45, 32), Fraction(27, 20)] + 5 * [Fraction(4, 3)]): print(', '.join(map(str, x)))
    45/32, 27/20, 4/3, 4/3, 4/3, 4/3, 4/3
    45/32, 4/3, 27/20, 4/3, 4/3, 4/3, 4/3
    45/32, 4/3, 4/3, 27/20, 4/3, 4/3, 4/3
    45/32, 4/3, 4/3, 4/3, 27/20, 4/3, 4/3
    45/32, 4/3, 4/3, 4/3, 4/3, 27/20, 4/3
    4/3, 45/32, 27/20, 4/3, 4/3, 4/3, 4/3
    4/3, 45/32, 4/3, 27/20, 4/3, 4/3, 4/3
    4/3, 45/32, 4/3, 4/3, 27/20, 4/3, 4/3
    4/3, 45/32, 4/3, 4/3, 4/3, 27/20, 4/3
    4/3, 4/3, 45/32, 27/20, 4/3, 4/3, 4/3
    4/3, 4/3, 45/32, 4/3, 27/20, 4/3, 4/3
    4/3, 4/3, 45/32, 4/3, 4/3, 27/20, 4/3
    4/3, 4/3, 4/3, 45/32, 27/20, 4/3, 4/3
    4/3, 4/3, 4/3, 45/32, 4/3, 27/20, 4/3
    4/3, 4/3, 4/3, 4/3, 45/32, 27/20, 4/3
    >>>
    >>> for x in marwa_permute([Fraction(45, 32), Fraction(81, 64), Fraction(64, 45)] + 4 * [Fraction(4, 3)]): print(', '.join(map(str, x)))
    45/32, 81/64, 64/45, 4/3, 4/3, 4/3, 4/3
    45/32, 81/64, 4/3, 64/45, 4/3, 4/3, 4/3
    45/32, 81/64, 4/3, 4/3, 64/45, 4/3, 4/3
    45/32, 81/64, 4/3, 4/3, 4/3, 64/45, 4/3
    45/32, 4/3, 81/64, 64/45, 4/3, 4/3, 4/3
    45/32, 4/3, 81/64, 4/3, 64/45, 4/3, 4/3
    45/32, 4/3, 81/64, 4/3, 4/3, 64/45, 4/3
    45/32, 4/3, 4/3, 81/64, 64/45, 4/3, 4/3
    45/32, 4/3, 4/3, 81/64, 4/3, 64/45, 4/3
    45/32, 4/3, 4/3, 4/3, 81/64, 64/45, 4/3
    4/3, 45/32, 81/64, 64/45, 4/3, 4/3, 4/3
    4/3, 45/32, 81/64, 4/3, 64/45, 4/3, 4/3
    4/3, 45/32, 81/64, 4/3, 4/3, 64/45, 4/3
    4/3, 45/32, 4/3, 81/64, 64/45, 4/3, 4/3
    4/3, 45/32, 4/3, 81/64, 4/3, 64/45, 4/3
    4/3, 45/32, 4/3, 4/3, 81/64, 64/45, 4/3
    4/3, 4/3, 45/32, 81/64, 64/45, 4/3, 4/3
    4/3, 4/3, 45/32, 81/64, 4/3, 64/45, 4/3
    4/3, 4/3, 45/32, 4/3, 81/64, 64/45, 4/3
    4/3, 4/3, 4/3, 45/32, 81/64, 64/45, 4/3
    >>>
    >>> for x in marwa_permute([Fraction(64, 45), Fraction(4, 3), Fraction(45, 32)] + 3 * [Fraction(4, 3)] + [Fraction(81, 64)]): print(', '.join(map(str, x)))
    64/45, 4/3, 45/32, 4/3, 4/3, 4/3, 81/64
    64/45, 4/3, 4/3, 45/32, 4/3, 4/3, 81/64
    64/45, 4/3, 4/3, 4/3, 45/32, 4/3, 81/64
    64/45, 4/3, 4/3, 4/3, 4/3, 45/32, 81/64
    4/3, 64/45, 4/3, 45/32, 4/3, 4/3, 81/64
    4/3, 64/45, 4/3, 4/3, 45/32, 4/3, 81/64
    4/3, 64/45, 4/3, 4/3, 4/3, 45/32, 81/64
    4/3, 4/3, 64/45, 4/3, 45/32, 4/3, 81/64
    4/3, 4/3, 64/45, 4/3, 4/3, 45/32, 81/64
    4/3, 4/3, 4/3, 64/45, 4/3, 45/32, 81/64
    """
    assert len(fourths) == 7
    special_positions = [i for i, f in enumerate(fourths[:-1]) if f != Fraction(4, 3)]

    num_free = len(fourths) - 1
    num_specials = len(special_positions)

    if num_specials == 0:
        return [list(fourths)]
    elif num_specials == 1:
        result = []
        for i in range(num_free):
            perm = num_free * [Fraction(4, 3)] + [fourths[-1]]
            perm[i] = fourths[special_positions[0]]
            result.append(perm)
        return result
    elif num_specials == 2:
        result = []
        for i in range(num_free - 1):
            for j in range(i + special_positions[1], num_free):
                perm = num_free * [Fraction(4, 3)] + [fourths[-1]]
                perm[i] = fourths[special_positions[0]]
                perm[j] = fourths[special_positions[1]]
                result.append(perm)
        return result
    elif num_specials == 3:
        result = []
        for i in range(num_free - 2):
            for j in range(i + special_positions[1], num_free - 1):
                for k in range(
                    j + special_positions[2] - special_positions[1], num_free
                ):
                    perm = num_free * [Fraction(4, 3)] + [fourths[-1]]
                    perm[i] = fourths[special_positions[0]]
                    perm[j] = fourths[special_positions[1]]
                    perm[k] = fourths[special_positions[2]]
                    result.append(perm)
        return result
    else:
        raise ValueError(num_specials)


def marwa(fourths):
    """
    Permutation N in the scl description corresponds to index N-1 in the returned list.

    >>> scales = marwa([Fraction(45, 32), Fraction(27, 20)] + 5 * [Fraction(4, 3)])
    >>> len(scales)
    15
    >>> scales[0]
    [Fraction(1, 1), Fraction(9, 8), Fraction(81, 64), Fraction(45, 32), Fraction(3, 2), Fraction(27, 16), Fraction(243, 128), Fraction(2, 1)]
    """
    return [stack(fs) for fs in marwa_permute(fourths)]
```


---


# Purvi modulations

As described on the [Marwa permutations](/constructions/marwa/) page, any seven-note
scale can be built by stacking variable-sized fourths. As with the Marwa
permutations, Wilson takes tetrachordal scales built from two of the same
tetrachord as his starting scales. For example, the scale built from Archytas'
diatonic tetrachord (steps 28/27, 8/7, 9/8)

```
1/1, 28/27, 32/27, 4/3, 3/2, 14/9, 16/9, 2/1
```

is built by stacking the fourths

```
4/3, 4/3, 4/3, 21/16, 4/3, 81/56, 4/3
```

Looking at the sizes of the fourths, we have

```
4/3     498¢
21/16   471¢
81/56   639¢
```

So 81/56 is atypical in size; we call it the closing fourth.

We form the Purvi modulations by first cyclically permuting the fourths to put
the closing fourth at the end:

```
4/3, 4/3, 4/3, 4/3, 21/16, 4/3, 81/56
```

and then cyclically permuting the first six fourths, keeping the closing fourth
fixed. For example, the first permutation gives the fourths:

```
4/3, 4/3, 4/3, 4/3, 4/3, 21/16, 81/56
```

and so the scale

```
1/1, 256/243, 32/27, 4/3, 112/81, 128/81, 16/9, 2/1
```

This construction gives six scales, from the six cyclic permutations of the
first six fourths.

The Purvi modulations always give a mode of a Marwa permutation, since the
Marwa permutations consider a wider set of permutations of the fourths.

## Further reading

- Erv Wilson, [The Purvi Modulations](https://anaphoria.com/xen10pur.pdf),
  Xenharmonikon 10 (1987)
- John Chalmers, [Divisions of the Tetrachord](https://eamusic.dartmouth.edu/~larry/published_articles/divisions_of_the_tetrachord/index.html),
  Frog Peak Music (1993), Chapter 6, p. 111

## Python code

```python
from fractions import Fraction
from math import floor, log2

F = Fraction


def tetrachordal_scale(a, b, c):
    """
    >>> tetrachordal_scale(Fraction(28, 27), Fraction(8, 7), Fraction(9, 8))
    [Fraction(1, 1), Fraction(28, 27), Fraction(32, 27), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(16, 9), Fraction(2, 1)]
    """
    steps = [a, b, c]
    return stack(steps + [Fraction(9, 8)] + steps)


def octave_reduce(x):
    if x == 2:
        return x
    return x * Fraction(2) ** (-floor(log2(x)))


def step_through(scale, step):
    """
    >>> step_through([Fraction(1, 1), Fraction(9, 8), Fraction(7, 6), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(7, 4), Fraction(2, 1)], 3)
    [Fraction(4, 3), Fraction(21, 16), Fraction(4, 3), Fraction(4, 3), Fraction(81, 56), Fraction(4, 3), Fraction(4, 3)]
    """
    N = len(scale) - 1
    i = 0
    result = []
    while len(result) < N:
        j = (i + step) % N
        result.append(octave_reduce(scale[j] / scale[i]))
        i = j
    return result


def stack(fs):
    """
    >>> stack([Fraction(4, 3), Fraction(21, 16), Fraction(4, 3), Fraction(4, 3), Fraction(81, 56), Fraction(4, 3), Fraction(4, 3)])
    [Fraction(1, 1), Fraction(9, 8), Fraction(7, 6), Fraction(4, 3), Fraction(3, 2), Fraction(14, 9), Fraction(7, 4), Fraction(2, 1)]
    """
    x = Fraction(1)
    notes = [Fraction(1)]
    for f in fs:
        x = octave_reduce(x * f)
        notes.append(x)
    return sorted(notes)


def rotate(xs, i):
    return xs[i:] + xs[:i]


def mode_rotate(scale, n):
    """Return the mode of scale starting on scale[n]."""
    period = scale[-1]
    notes = scale[:-1]
    n = n % len(notes)
    tonic = notes[n]
    rotated = notes[n:] + [period * x for x in notes[:n]]
    return sorted(octave_reduce(x / tonic) for x in rotated) + [period]


def purvi_permutations(a, b, c, closing_fourth):
    """
    >>> scales = purvi_permutations(Fraction(8, 7), Fraction(9, 8), Fraction(28, 27), Fraction(81, 56))
    >>> len(scales)
    6
    >>> scales[0]
    [Fraction(1, 1), Fraction(28, 27), Fraction(32, 27), Fraction(4, 3), Fraction(112, 81), Fraction(14, 9), Fraction(16, 9), Fraction(2, 1)]
    """
    # Returns the 6 scales from the cyclic permutations described in purvi.md.
    ts = tetrachordal_scale(a, b, c)
    fourths = step_through(ts, 3)
    rot_fourths = rotate(fourths, -((len(fourths) - 1) - fourths.index(closing_fourth)))
    return [stack(rotate(rot_fourths[:-1], i) + [rot_fourths[-1]]) for i in range(6)]


def purvi(a, b, c, closing_fourth):
    # Returns the 7 modulations matching Wilson's figures.
    # The mode rotation (4-3*i)%7 was reverse-engineered to match Wilson's figures.
    raw = purvi_permutations(a, b, c, closing_fourth)
    return [mode_rotate(raw[i % 6], (4 - 3 * i) % 7) for i in range(7)]
```


---


# Moments of symmetry

A moment of symmetry is a scale built by repeatedly stacking a generating
interval and reducing it within a period. In general, this stacking process
gives a scale with three step sizes; we have a moment of symmetry when we get
only two step sizes. For example, stacking a generator of 707¢ within a period
of 1200¢ seven times gives the scale

```
0, 214, 428, 642, 707, 921, 1135, 1200
```

with two step sizes 65¢ and 214¢, so this scale is a moment of symmetry.

Stacking the generator six times gives the scale

```
0, 214, 428, 707, 921, 1135, 1200
```

with three step sizes 65¢, 214¢, 279¢, so this scale is not a moment of
symmetry.

## Further reading

- [Introduction to Erv Wilson's Moments of Symmetry](https://www.anaphoria.com/wilsonintroMOS.html), The Wilson Archives

## Python code

```python
"""
Moments of Symmetry.
"""

import math
from decimal import Decimal


def steps(scale):
    return [b - a for a, b in zip(scale, scale[1:])]


def stern_brocot(generator, period, max_n=50):
    """
    Return Stern-Brocot tree entries ((a, c), (b, d)) for the given generator
    and period, where a/c and b/d are the left and right search interval
    endpoints.

    Each entry gives a MOS of size n = c + d, with generator at scale degree
    m = a + b, step sizes

        s = (d * generator - b * period) / delta
        t = (-c * generator + a * period) / delta

    where delta = a * d - b * c = -1, and c steps of s and d steps of t.

    >>> stern_brocot(Decimal('707'), Decimal('1200'), 5)
    [((0, 1), (1, 0)), ((0, 1), (1, 1)), ((1, 2), (1, 1)), ((1, 2), (2, 3))]
    """
    a, c = 0, 1  # left  = 0/1
    b, d = 1, 0  # right = 1/0 (infinity)
    result = []
    while True:
        m, n = a + b, c + d
        if n > max_n:
            break
        result.append(((a, c), (b, d)))
        if d == 0 or generator * n < period * m:
            b, d = m, n  # mediant is to the right of generator/period
        else:
            a, c = m, n  # mediant is to the left of generator/period
    return result


def mos_sizes(generator, period, max_n=50):
    """
    >>> mos_sizes(Decimal('707'), Decimal('1200'), 20)
    [1, 2, 3, 5, 7, 12, 17]
    """
    return [c + d for (a, c), (b, d) in stern_brocot(generator, period, max_n)]


def repeat_scale(scale, k):
    """Repeat a sub-period scale (starting at 0, ending at period) k times."""
    result = scale
    for _ in range(k - 1):
        result = result + [x + result[-1] for x in scale[1:]]
    return result


def mos(generator, period, n, rotation=0, repeat=1):
    """
    Generate an n-note moment of symmetry scale.

    Stack the generator n times modulo the period and sort. `rotation` selects the
    mode. `repeat` copies the result for multi-period scales.

    >>> mos(Decimal('707'), Decimal('1200'), 5)
    [Decimal('0'), Decimal('214'), Decimal('428'), Decimal('707'), Decimal('921')]

    >>> mos(Decimal('707'), Decimal('1200'), 7)
    [Decimal('0'), Decimal('214'), Decimal('428'), Decimal('642'), Decimal('707'), Decimal('921'), Decimal('1135')]
    """
    generator = Decimal(str(generator))
    period = Decimal(str(period))

    notes = sorted((k * generator) % period for k in range(n))
    scale = notes + [period]
    step_sizes = steps(scale)

    # The Stern-Brocot characterisation of MOS is equivalent to the step-size
    # characterisation above: every size in mos_sizes gives a
    # scale with exactly two step sizes, and vice versa.
    assert n in mos_sizes(generator, period, n), f"{n} not a valid MOS size"
    assert len(set(step_sizes)) <= 2

    # The Stern-Brocot parents for the MOS determine step sizes and step counts
    # These formulae can be derived by mapping the MOS to a two-dimensional keyboard
    for (a, c), (b, d) in stern_brocot(generator, period, n):
        if c + d == n:
            break

    if generator in notes:
        assert notes.index(generator) == a + b

    delta = a * d - b * c
    assert delta == -1
    s = (d * generator - b * period) / delta
    t = (-c * generator + a * period) / delta

    # c steps of s and d steps of t; only include a size if it appears.
    assert set(step_sizes) == ({s} if d == 0 else {s, t})
    if s != t:
        assert step_sizes.count(s) == c and step_sizes.count(t) == d

    # (a, b) is determined by (c, d) and generator/period
    if d > 0:
        assert a == math.floor(generator * c / period)
        assert b == math.ceil(generator * d / period)

    # Return requested mode and repeat

    shift = notes[rotation]
    tail = [x - shift for x in notes[rotation:]]
    head = [x - shift + period for x in notes[:rotation]]
    notes = tail + head
    scale = repeat_scale(notes + [period], repeat)

    return scale[:-1]
```


---


# Secondary moments of symmetry

A secondary moment of symmetry (MOS) is built from a larger parent MOS by
stepping through it in steps of a given size, wrapping at the period. For
example, take the parent MOS as the seven-note scale built by stacking
4/3

```
1/1, 9/8, 81/64, 4/3, 3/2, 27/16, 243/128
```

Stepping through five times in steps of three scale degrees, we can choose to
start on each of the seven scale degrees, giving the seven sets of notes

```
1:  1/1,     4/3,     243/128, 81/64,   27/16
2:  9/8,     3/2,     1/1,     4/3,     243/128
3:  81/64,   27/16,   9/8,     3/2,     1/1
4:  4/3,     243/128, 81/64,   27/16,   9/8
5:  3/2,     1/1,     4/3,     243/128, 81/64
6:  27/16,   9/8,     3/2,     1/1,     4/3
7:  243/128, 81/64,   27/16,   9/8,     3/2
```

Sorting and taking the lowest note in each scale as tonic gives

```
1:  1/1,     81/64,   4/3,     27/16,   243/128
2:  1/1,     9/8,     4/3,     3/2,     243/128
3:  1/1,     9/8,     81/64,   3/2,     27/16
4:  1/1,     9/8,     32/27,   3/2,     27/16
5:  1/1,     81/64,   4/3,     3/2,     243/128
6:  1/1,     9/8,     4/3,     3/2,     27/16
7:  1/1,     9/8,     4/3,     3/2,     27/16
```

This is a family of secondary MOS called the Tanabe cycle.

Only five of the seven scales are unique (not modes of each other).

The ordinary five-note MOS from stacking 4/3 has two step sizes

```
S = 9/8
T = 32/27
```

The family of secondary MOS together use four step sizes
```
S1 = 9/8
S2 = 256/243
T1 = 32/27
T2 = 81/64
```

In general, starting with an N-note parent MOS, which has two step sizes, we get
a family of n distinct n-note secondary MOS (where n < N), together using four
step sizes.

## Further reading

- [Introduction to Erv Wilson's Moments of Symmetry](https://www.anaphoria.com/wilsonintroMOS.html), The Wilson Archives

## Python code

```python
from fractions import Fraction
from math import floor, log2


def step_through(s, n, step, start=0):
    """
    >>> parent = [Fraction(1, 1), Fraction(9, 8), Fraction(81, 64), Fraction(4, 3), Fraction(3, 2), Fraction(27, 16), Fraction(243, 128)]
    >>> step_through(parent, 5, step=3, start=0)
    [Fraction(1, 1), Fraction(81, 64), Fraction(4, 3), Fraction(27, 16), Fraction(243, 128)]
    >>> step_through(parent, 5, step=3, start=3)
    [Fraction(9, 8), Fraction(81, 64), Fraction(4, 3), Fraction(27, 16), Fraction(243, 128)]
    """
    N = len(s)
    count = 0
    i = start
    t = []
    while count < n:
        t.append(s[i])
        i = (i + step) % N
        count += 1
    return sorted(t)


def secondary_mos_family(parent_mos, n, step):
    """
    >>> parent = [Fraction(1, 1), Fraction(9, 8), Fraction(81, 64), Fraction(4, 3), Fraction(3, 2), Fraction(27, 16), Fraction(243, 128)]
    >>> for x in secondary_mos_family(parent, 5, step=3): print(x)
    [Fraction(1, 1), Fraction(81, 64), Fraction(4, 3), Fraction(27, 16), Fraction(243, 128)]
    [Fraction(1, 1), Fraction(9, 8), Fraction(4, 3), Fraction(3, 2), Fraction(243, 128)]
    [Fraction(1, 1), Fraction(9, 8), Fraction(81, 64), Fraction(3, 2), Fraction(27, 16)]
    [Fraction(1, 1), Fraction(9, 8), Fraction(32, 27), Fraction(3, 2), Fraction(27, 16)]
    [Fraction(1, 1), Fraction(81, 64), Fraction(4, 3), Fraction(3, 2), Fraction(243, 128)]
    [Fraction(1, 1), Fraction(9, 8), Fraction(4, 3), Fraction(3, 2), Fraction(27, 16)]
    [Fraction(1, 1), Fraction(9, 8), Fraction(4, 3), Fraction(3, 2), Fraction(27, 16)]
    """
    result = []
    for i in range(len(parent_mos)):
        scale = step_through(parent_mos, n, step, start=i)
        transposed_scale = [x / scale[0] for x in scale]
        result.append(transposed_scale)

    assert len(set(standard_mode_steps(x) for x in result)) == n

    return result


def find_secondary_mos(generator, N, n, step):
    """
    >>> family = find_secondary_mos(Fraction(4, 3), 7, 5, 3)
    >>> len(family)
    7
    >>> family = find_secondary_mos(Fraction(4, 3), 17, 7, 5)
    >>> len(family)
    17
    """
    parent = sorted(reduce(generator**i) for i in range(N))
    family = secondary_mos_family(parent, n, step)

    analytic_step_sizes = secondary_mos_step_sizes(
        generator, parent.index(generator), N, step, n
    )
    computed_step_sizes = {s for scale in family for s in steps(scale)}

    assert set(analytic_step_sizes) == computed_step_sizes

    return family


def steps(scale):
    scale = scale + [Fraction(2)]
    return tuple(y / x for x, y in zip(scale, scale[1:]))


def standard_mode_steps(scale):
    """
    >>> scale = [Fraction(1, 1), Fraction(81, 64), Fraction(4, 3), Fraction(27, 16), Fraction(243, 128)]
    >>> standard_mode_steps(scale)
    (Fraction(81, 64), Fraction(9, 8), Fraction(256, 243), Fraction(81, 64), Fraction(256, 243))
    """
    s = steps(scale)
    return max(s[i:] + s[:i] for i in range(len(s)))


def reduce(x):
    return x * Fraction(2) ** (-floor(log2(x)))


def stern_brocot(num, denom):
    """
    >>> stern_brocot(5, 17)
    [((0, 1), (1, 0)), ((0, 1), (1, 1)), ((0, 1), (1, 2)), ((0, 1), (1, 3)), ((1, 4), (1, 3)), ((2, 7), (1, 3)), ((2, 7), (3, 10))]
    """
    a, c = 0, 1  # left  = 0/1
    b, d = 1, 0  # right = 1/0 (infinity)
    result = []
    while True:
        m, n = a + b, c + d
        result.append(((a, c), (b, d)))
        if (m, n) == (num, denom):
            break
        if d == 0 or num * n < denom * m:
            b, d = m, n  # mediant is to the right of fraction
        else:
            a, c = m, n  # mediant is to the left of fraction
    return result


def secondary_mos_step_sizes(G, M, N, step, n):
    """
    Analytic formulae for secondary MOS family step sizes

    These can be derived by mapping the parent MOS to a two-dimensional keyboard.
    `G` is the generator of the parent MOS, e.g. 4/3. `M` is the index of the
    generator in the parent MOS. `N` is the size of the parent MOS. `step` is the
    step used to find the secondary MOS by stepping through the parent MOS.  `n` is
    the number of notes in the secondary MOS.

    Examples from Wilson's MOS letter:

    Tanabe cycle:

    >>> secondary_mos_step_sizes(Fraction(4, 3), 3, 7, 3, 5)
    (Fraction(9, 8), Fraction(256, 243), Fraction(32, 27), Fraction(81, 64))

    Cycle of 17 scales:

    >>> secondary_mos_step_sizes(Fraction(4, 3), 7, 17, 5, 7)
    (Fraction(2187, 2048), Fraction(65536, 59049), Fraction(16777216, 14348907), Fraction(9, 8))

    """
    M_inv = pow(M, -1, N)
    k = (M_inv * step) % N

    # Secondary MOS size n must be a valid mos size for step/N
    c, d = None, None
    for (a, c), (b, d) in stern_brocot(step, N):
        if c + d == n:
            break
    assert c is not None, f"{n} not a valid MOS size for {step}/{N}"

    neg_kd = (-k * d) % N
    kc = (k * c) % N

    # Analytic formulae for secondary MOS step sizes
    S1 = reduce(G ** (neg_kd - N))
    S2 = reduce(G**neg_kd)
    T1 = reduce(G**kc)
    T2 = reduce(G ** (kc - N))

    # Equal ratio property
    assert S2 / S1 == T1 / T2

    # Equal ratio between two smallest and two largest steps
    sorted_steps = sorted({S1, S2, T1, T2})
    assert sorted_steps[1] / sorted_steps[0] == sorted_steps[3] / sorted_steps[2]

    return (S1, S2, T1, T2)
```


---


# Recurrent sequence scales

Given a recurrent sequence like the Fibonacci sequence

```
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
```

pick a start point and end point, say 3 and 34, take the segment between them

```
3, 5, 8, 13, 21, 34
```

divide by the smallest term

```
3/3, 5/3, 8/3, 13/3, 21/3, 34/3
```

then octave-reduce and sort to give the scale

```
1/1, 13/12, 4/3, 17/12, 5/3, 7/4
```

with period 2/1.

As you move along a recurrent sequence, the ratio between successive terms
approaches a limit (1.618, or 833¢, for the Fibonacci sequence). This means
that segments far along the sequence approach scales built by stacking a
generator. This gives a connection to [moments of
symmetry](/constructions/mos).

## Further reading

- Warren Burt, [Developing and Composing With Scales based on Recurrent
  Sequences](http://www.warrenburt.com/storage/selected_articles/Burt2002bRecurrentSequencesACMC02.pdf)
- [An introduction to the scales of Mt Meru and other recurrent sequence
  scales](https://www.anaphoria.com/wilsonintroMERU.html), The Wilson Archives

## Python code

```python
"""
>>> scale1 = recurrent_sequence_scale((1, 1), (1, 1), 3, 8)
>>> scale2 = recurrent_sequence_scale((1, 1), (1, 1), 6, 11)
>>> scale3 = sorted((i * 833) % 1200 for i in range(6))
>>> print([round(1200 * log2(x)) for x in scale1])
[0, 139, 498, 603, 884, 969]
>>> print([round(1200 * log2(x)) for x in scale2])
[0, 97, 464, 563, 830, 930]
>>> print(scale3)
[0, 99, 466, 565, 833, 932]
"""

from fractions import Fraction
from math import log2, floor


def reduce(x):
    return x * Fraction(2) ** (-floor(log2(x)))


def recurrent_sequence_scale(coeffs, seed, start, stop):
    """
    >>> recurrent_sequence_scale((1, 1), (1, 1), 3, 8)
    [Fraction(1, 1), Fraction(13, 12), Fraction(4, 3), Fraction(17, 12), Fraction(5, 3), Fraction(7, 4)]
    """
    seq = list(seed)
    while len(seq) <= stop:
        seq.append(sum(c * seq[-i] for i, c in enumerate(coeffs, 1)))
    segment = seq[start : stop + 1]
    return sorted(set(reduce(Fraction(x, segment[0])) for x in segment))
```


---


# Equal divisions

Divide a period into N equal parts. For example, an equal division of 1200¢ into
5 parts is given by

```
0, 240, 480, 720, 960, 1200
```

An equal division of the interval 3/1, or 1902¢, into 13 parts gives the
Bohlen-Pierce scale:

```
0, 146, 293, 439, 585, 732, 878, 1024, 1170, 1317, 1463, 1609, 1756, 1902
```

## Further reading

- Augusto Novaro, [Sistema Natural de la Música](https://www.anaphoria.com/novaro51.pdf), p. 53, (1951)
- Wendy Carlos, Tuning: At the Crossroads, Computer Music Journal 11.1 (1987), pp. 29–43

## Python code

```python
def equal_division(period, n):
    """
    >>> equal_division(1200, 5)
    [0.0, 240.0, 480.0, 720.0, 960.0, 1200.0]
    >>> [round(x) for x in equal_division(1902, 13)]
    [0, 146, 293, 439, 585, 732, 878, 1024, 1170, 1317, 1463, 1609, 1756, 1902]
    """
    return [i * period / n for i in range(n + 1)]
```


---


# Means

## Arithmetic mean

The arithmetic mean of two numbers x and y is

```
(x + y)/2
```

The arithmetic mean of 1/1 and 2/1 is 3/2.

We can make a scale by starting with 1/1 and 2/1 and repeatedly adding
arithmetic means between neighbouring terms. This gives the scales

```
0:  1/1,                                       2/1
1:  1/1,                3/2,                   2/1
2:  1/1,      5/4,      3/2,        7/4,       2/1
3:  1/1, 9/8, 5/4, 11/8, 3/2, 13/8, 7/4, 15/8, 2/1
```

These are all harmonic series segments.

## Harmonic mean

The harmonic mean of two numbers x and y is

```
2/(1/x + 1/y)
```

The harmonic mean of 1/1 and 2/1 is 4/3.

Repeatedly adding harmonic means between 1/1 and 2/1 gives the scales

```
0:  1/1,                                           2/1
1:  1/1,                    4/3,                   2/1
2:  1/1,        8/7,        4/3,        8/5,       2/1
3:  1/1, 16/15, 8/7, 16/13, 4/3, 16/11, 8/5, 16/9, 2/1
```

These are all subharmonic series segments.

## Further reading

- Richard L. Crocker, Pythagorean mathematics and music, The Journal of
  Aesthetics and Art Criticism 22.3 (1964) pp. 325-335
- John Chalmers, [Divisions of the Tetrachord](https://eamusic.dartmouth.edu/~larry/published_articles/divisions_of_the_tetrachord/index.html),
  Frog Peak Music (1993), Chapter 4, p. 29

## Python code

```python
"""
>>> scale1 = iterated_arithmetic_mean([Fraction(1, 1), Fraction(2, 1)], 3)
>>> harmonic_series_segment = [Fraction(i, 2**3) for i in range(2**3, 2**4 + 1)]
>>> assert scale1 == harmonic_series_segment

>>> scale2 = iterated_harmonic_mean([Fraction(1, 1), Fraction(2, 1)], 3)
>>> subharmonic_series_segment = sorted(Fraction(2**4, i) for i in range(2**3, 2**4 + 1))
>>> assert scale2 == subharmonic_series_segment
"""

from fractions import Fraction


def iterated_arithmetic_mean(s, k):
    """
    >>> iterated_arithmetic_mean([Fraction(1, 1), Fraction(2, 1)], 1)
    [Fraction(1, 1), Fraction(3, 2), Fraction(2, 1)]
    >>> iterated_arithmetic_mean([Fraction(1, 1), Fraction(2, 1)], 2)
    [Fraction(1, 1), Fraction(5, 4), Fraction(3, 2), Fraction(7, 4), Fraction(2, 1)]
    >>> iterated_arithmetic_mean([Fraction(1, 1), Fraction(2, 1)], 3)
    [Fraction(1, 1), Fraction(9, 8), Fraction(5, 4), Fraction(11, 8), Fraction(3, 2), Fraction(13, 8), Fraction(7, 4), Fraction(15, 8), Fraction(2, 1)]
    """
    result = list(s)
    for _ in range(k):
        new = [(x + y) / 2 for x, y in zip(result, result[1:])]
        result = sorted(result + new)
    return result


def iterated_harmonic_mean(s, k):
    """
    >>> iterated_harmonic_mean([Fraction(1, 1), Fraction(2, 1)], 1)
    [Fraction(1, 1), Fraction(4, 3), Fraction(2, 1)]
    >>> iterated_harmonic_mean([Fraction(1, 1), Fraction(2, 1)], 2)
    [Fraction(1, 1), Fraction(8, 7), Fraction(4, 3), Fraction(8, 5), Fraction(2, 1)]
    >>> iterated_harmonic_mean([Fraction(1, 1), Fraction(2, 1)], 3)
    [Fraction(1, 1), Fraction(16, 15), Fraction(8, 7), Fraction(16, 13), Fraction(4, 3), Fraction(16, 11), Fraction(8, 5), Fraction(16, 9), Fraction(2, 1)]
    """
    result = list(s)
    for _ in range(k):
        new = [2 / (1 / x + 1 / y) for x, y in zip(result, result[1:])]
        result = sorted(result + new)
    return result
```


---
