Skip to content

Custom TET

Nothing about the tuning system is hard-coded. A TET step is just an expression, so any base and any number of divisions is one line of DSL away. This page shows how to build a scale, which bases behave well, and how to get the result back into the library.

The formula

To divide interval I into N equal steps, one step is I ^ (1/N):

2 ^ (1/17)      # 17 equal divisions of the octave
2 ^ (1/53)      # 53-TET
3 ^ (1/8)       # 8 equal divisions of the tritave
5 ^ (1/5)       # 5 equal divisions of 5/1

k steps is I ^ (k/N).

Legacy JavaScript syntax
javascript
new Fraction(2).pow(new Fraction(1, 17))
new Fraction(3).pow(new Fraction(1, 8))

Use integer bases

Non-integer bases are not kept symbolic

MusicValue.pow only builds a symbolic power for positive integer bases. Give it anything else — (1618/1000) for a golden-ratio scale, say — and it falls straight through to Math.pow() on floats.

Every step of such a scale is then raw floating-point, every downstream multiplication compounds the error, and the scale will not close exactly at the top. Simplification cannot merge the terms either, because there is no symbolic term to merge.

2^(1/17), 3^(1/8) and 5^(1/5) are fine. (1618/1000)^(1/7) is not — it will play, but none of the exactness guarantees apply to it.

If you want a non-integer interval of equivalence, the honest options are to accept the float behaviour knowingly, or to pick an integer-based approximation of the interval you actually want.

Building the scale

Chain each note off the previous one, exactly as the shipped modules do. Create notes from the note widget's Add Note / Silence section (choose At End, then Create Note — see Creating Notes), then set each one's frequency in the Raw: field and press Save.

# note 1 — the root
base.f
# note 2
[1].f * 2 ^ (1/17)
# note 3
[2].f * 2 ^ (1/17)
# … and so on, seventeen times

Note 18 comes out at exactly 2 × base.f. Only note 1 touches base, which makes the scale a chain: drag note 1 and the whole thing transposes.

Set start times the same way, so each note follows the last:

[1].t + [1].d

And give every note a duration in beats rather than seconds, so the scale follows the tempo:

beat(base) * (1/2)

"duration": "1" is one second, not one beat

A bare number is seconds. It will not track the tempo. Every shipped scale module uses beat(base) * (1/2) or similar. Use beat(base).

Divisions worth trying

SystemStep sizeCharacter
5-TET240.00¢near-equal pentatonic; Indonesian slendro territory
7-TET171.43¢near-equal heptatonic; Thai classical territory
17-TET70.59¢fifth +3.9¢; thirds are 33¢ out, so 5-limit harmony is off the table
22-TET54.55¢major third −4.5¢, but the fifth runs +7.1¢ sharp
24-TET50.00¢quarter tones; contains 12-TET exactly
53-TET22.64¢nearly perfect fifths and thirds
72-TET16.67¢contains 12-TET; very fine pitch control

53-TET is the standout. Its fifth is 0.1 cents off pure and its major third is 1.4 cents off — both far better than 31-TET manages:

IntervalJust12-TET19-TET31-TET53-TET
Perfect fifth (steps)3/27111831
— error−2.0¢−7.2¢−5.2¢−0.1¢
Major third (steps)5/4461017
— error+13.7¢−7.4¢+0.8¢−1.4¢
Minor third (steps)6/535814
— error−15.6¢+0.1¢−6.0¢+1.3¢

The price is 53 notes per octave.

Non-octave systems

The base does not have to be 2.

3 ^ (1/8)       # 8 equal divisions of the tritave
5 ^ (1/5)       # 5 equal divisions of 5/1

Bohlen–Pierce is the worked example: 13 equal divisions of 3/1. The shipped Mixed-Base module goes further and alternates bases within one scale — 2^(1/12), then 3^(1/13), then a 5^(1/7) — which works because RMT keeps powers of different bases as separate terms instead of collapsing them into a single float.

Getting it into the library

"Save Module" does not add anything to the library

Save Module (in the + menu) downloads your composition as a file called module.json. That is all it does. It does not create a library tile, and there is no menu step that turns a downloaded file into one.

There are two real paths.

Copy to Modules (the in-app one)

  1. Select the scale's notes — drag a marquee around them, or long-press on mobile.
  2. The group widget appears.
  3. Click Copy to Modules.

It saves the selection as a module in the library's Custom section, rooted at its earliest note and keeping the dependency tree intact. The Custom section expands automatically if it was collapsed. Your scale is then a draggable tile like any other, and you can find it with the library search.

Upload a file

Click the dashed + placeholder at the end of any library section and pick a .json file. Invalid modules are rejected with the validation errors.

Module JSON

If you are hand-writing the file, this is the shape the shipped TET modules use:

json
{
  "baseNote": {
    "startTime": "0",
    "frequency": "440",
    "tempo": "100",
    "beatsPerMeasure": "4",
    "measureLength": "beat(base) * base.bpm",
    "instrument": "sine-wave"
  },
  "notes": [
    {
      "id": 1,
      "startTime": "base.t",
      "duration": "beat(base) * (1/2)",
      "frequency": "base.f",
      "color": "rgba(100,149,237,0.7)"
    },
    {
      "id": 2,
      "startTime": "[1].t + [1].d",
      "duration": "beat(base) * (1/2)",
      "frequency": "[1].f * 2 ^ (1/17)",
      "color": "rgba(100,149,237,0.7)"
    }
  ]
}

Four things to copy from it:

  • "measureLength": "beat(base) * base.bpm" on the BaseNote. Without it the measure grid will not match your tempo.
  • instrument set once on the BaseNote. Notes inherit it; you do not need it on every note.
  • duration in beats, not seconds.
  • color on every note. Notes without one fall back to default colouring.

Expressions may reference base or any note inside the same module — a module must be self-contained, or it cannot be dropped onto an arbitrary note. Every module the app ships is written in DSL. The legacy method-chain format still loads, but nothing ships in it any more.

What to expect once it loads

Every note will show ≈ and cross-hatching. 2^(k/17) is irrational, so the frequency is flagged. The fraction drawn beside the ≈ is the note's approximate ratio to the BaseNote (maximum denominator 8192), not its stored value.

The arrows will not step by your scale. ▲/▼ apply a rational interval configured in Settings → Arrows — positive integers, ratio within [1/16, 16]. A TET step is not expressible that way. Use the arrows to move octaves (or fifths, or commas); edit the exponent to move by degrees.

Simplification will tidy the expression on save. 2^(1/17) * 2^(1/17) * base.f saves as 2^(2/17) * base.f. And a perfect root resolves to a rational — 4^(1/2) * base.f becomes 2 * base.f, and the note stops being flagged at all.

Why some numbers work

Divisions of the octave that approximate just intervals well are the convergents of continued fractions of log₂(3/2) and log₂(5/4). That is the whole trick behind 12, 19, 31 and 53 — they are the divisions where the fifth and third happen to land close to a step boundary.

No equal temperament matches every just interval. The mismatch has a name — a comma:

CommaSizeWhat it is
Schisma2.0¢the gap between a Pythagorean and a syntonic comma
Diaschisma19.6¢3 octaves vs. 4 fifths + 2 major thirds
Syntonic comma21.5¢4 fifths vs. a major third + 2 octaves
Pythagorean comma23.5¢12 fifths vs. 7 octaves

Each temperament is a decision about where to hide these. All four ship as interval modules in the library's Intervals section, so you can hear them.

Next steps

Released under the MIT License