Skip to content

Scale catalog & two-tier diatonicity

This is the human-readable form of the cross-port scale contract. The blessed conformance cases under conformance/music-dsl/cases/ are authoritative; the normative source is conformance/music-dsl/SPEC-scales.md, and every port (Python / TypeScript / Rust) implements to it. This page mirrors that spec.

1. Scale representation

A scale is a 12-bit pitch-class set (bit 11-pc set means the pitch class pc semitones above the tonic is in the scale), repeated into a 36-bit mask. The triple copy lets the modal-alignment scan rotate the scale to any root without the sliding window falling off the most-significant end. scale_value(name) returns this mask and is the core contract — identical masks across ports guarantee identical membership.

Each scale carries a descriptor: mask, canonical name, category, and supports_diatonic_function (the two-tier flag).

2. The two-tier model (NORMATIVE)

Tonalis separates two questions that are often conflated, because they are not equally meaningful for every scale.

Tier 1 — membership

contains(scale, pitch_class) is defined for every scale, functional or not. It returns whether pitch_class % 12 (semitones above the tonic) is in the scale.

Algorithm: take the leading 12-bit copy of the mask; the pitch class pc is present iff bit (11 - (pc % 12)) is set.

Tier 2 — function

is_diatonic (and any harmonic-function query) is defined only on scales with supports_diatonic_function = true. On a non-functional scale it MUST refuse — idiomatically per language:

Port Refusal
Python raises NonFunctionalScaleError
TypeScript throws
Rust returns Err

Never a value. A functional query against a non-functional scale that returns a value is a conformance failure (the refuse-* cases assert this).

Why two tiers

Diatonic function presupposes a tonal hierarchy — a tonic, and degrees that resolve toward it. The symmetric and atonal scales (whole-tone, the two diminished scales, augmented, chromatic) have no such hierarchy: every note is equivalent under the scale's own symmetry, so "is this chord diatonic / what is its harmonic function" has no meaningful answer. Rather than return an answer that looks authoritative but isn't, those scales refuse the functional query — while still answering membership, which is always well-defined. Non-functional scales are exactly the symmetric/atonal set in §3; everything else is functional.

3. The catalog

Pitch classes are the semitones above the tonic. This table is generated from the live music_dsl catalog on every docs build, so it cannot drift from the code or the conformance corpus.

39 scales — generated from the music_dsl catalog, so this table cannot drift from the conformance corpus.

Functional scales (34)

contains and is_diatonic / harmonic-function queries are defined.

Scale Category Semitones (from tonic) Diatonic queries
Major (Ionian) major-mode 0 2 4 5 7 9 11 ✅ defined
Dorian major-mode 0 2 3 5 7 9 10 ✅ defined
Phrygian major-mode 0 1 3 5 7 8 10 ✅ defined
Lydian major-mode 0 2 4 6 7 9 11 ✅ defined
Mixolydian major-mode 0 2 4 5 7 9 10 ✅ defined
Natural minor (Aeolian) major-mode 0 2 3 5 7 8 10 ✅ defined
Locrian major-mode 0 1 3 5 6 8 10 ✅ defined
Melodic minor melodic-minor 0 2 3 5 7 9 11 ✅ defined
Dorian b2 melodic-minor 0 1 3 5 7 9 10 ✅ defined
Lydian augmented melodic-minor 0 2 4 6 8 9 11 ✅ defined
Lydian dominant melodic-minor 0 2 4 6 7 9 10 ✅ defined
Mixolydian b6 melodic-minor 0 2 4 5 7 8 10 ✅ defined
Locrian natural 2 melodic-minor 0 2 3 5 6 8 10 ✅ defined
Altered (Super Locrian) melodic-minor 0 1 3 4 6 8 10 ✅ defined
Harmonic minor harmonic-minor 0 2 3 5 7 8 11 ✅ defined
Locrian natural 6 harmonic-minor 0 1 3 5 6 9 10 ✅ defined
Ionian #5 harmonic-minor 0 2 4 5 8 9 11 ✅ defined
Dorian #4 (Ukrainian) harmonic-minor 0 2 3 6 7 9 10 ✅ defined
Phrygian dominant harmonic-minor 0 1 4 5 7 8 10 ✅ defined
Lydian #2 harmonic-minor 0 3 4 6 7 9 11 ✅ defined
Ultralocrian harmonic-minor 0 1 3 4 6 8 9 ✅ defined
Harmonic major harmonic-major 0 2 4 5 7 8 11 ✅ defined
Double harmonic (Byzantine) exotic 0 1 4 5 7 8 11 ✅ defined
Hungarian minor exotic 0 2 3 6 7 8 11 ✅ defined
Hungarian major exotic 0 3 4 6 7 9 10 ✅ defined
Neapolitan major exotic 0 1 3 5 7 9 11 ✅ defined
Neapolitan minor exotic 0 1 3 5 7 8 11 ✅ defined
Major pentatonic pentatonic 0 2 4 7 9 ✅ defined
Minor pentatonic pentatonic 0 3 5 7 10 ✅ defined
Blues (minor) blues 0 3 5 6 7 10 ✅ defined
Bebop dominant bebop 0 2 4 5 7 9 10 11 ✅ defined
Bebop major bebop 0 2 4 5 7 8 9 11 ✅ defined
Bebop Dorian bebop 0 2 3 4 5 7 9 10 ✅ defined
Bebop minor bebop 0 2 3 5 7 9 10 11 ✅ defined

Symmetric / atonal scales (5)

Membership (contains) works; functional queries refuse (Python raises, TS throws, Rust Err).

Scale Category Semitones (from tonic) Diatonic queries
Whole tone symmetric 0 2 4 6 8 10 ⛔ refuses
Diminished (half-whole) symmetric 0 1 3 4 6 7 9 10 ⛔ refuses
Diminished (whole-half) symmetric 0 2 3 5 6 8 9 11 ⛔ refuses
Augmented symmetric 0 3 4 7 8 11 ⛔ refuses
Chromatic atonal 0 1 2 3 4 5 6 7 8 9 10 11 ⛔ refuses

The bebop scales are the 7-note parent plus one chromatic passing tone (dominant: the major 7th over Mixolydian; major: the #5 over the major scale).

4. Bebop minor — both forms named

"Bebop minor" names two different scales in the literature, so both are in the catalog under distinct names rather than blessing one as the sole "bebop minor":

  • Bebop Dorian — Dorian + a natural-3 passing tone (0 2 3 4 5 7 9 10), for the ii−7 chord.
  • Bebop minor — Dorian + a natural-7 passing tone (0 2 3 5 7 9 10 11), paralleling the bebop dominant's added-note logic.