Skip to content

Music Theory

The music theory module turns musical ideas — scales, keys, chords, and intervals — into values you can compose with, so you can write melodies in a key, build chords by name, and explore modes and microtonal tunings without working out every MIDI number by hand. It scales from the familiar 12-tone system all the way to custom and Scala-based tunings.

Resonon separates two ideas that are easy to confuse:

  • A Scale is rootless — just a set of intervals (e.g. major = 0 2 4 5 7 9 11). It has no pitch until you anchor it.
  • A Key is a Scale rooted at a note. It is the playable object: it turns scale degrees into notes, builds diatonic chords, and quantizes pitches.

Everything that produces actual notes lives on Key (or on the standalone chord() / interval() builders). Scale only describes intervals.

Scale is a first-class value. Build one from a built-in name or from a custom semitone array — including microtonal tunings with fractional semitones.

// From a built-in name
let major = Scale("major");
// From a semitone interval array (must start with 0, strictly ascending)
let custom = Scale(#[0, 2, 4, 5, 7, 9, 11]); // same as major
// Microtonal: fractional semitones
let neutral = Scale(#[0, 1.5, 3.5, 5, 7, 8.5, 10]);
// Non-octave tuning: pass a custom period (semitones per repeat)
let edo19 = Scale(#[0, 2, 4, 5, 7, 9, 11], 19);
MethodReturnsDescription
s.name()StringScale name ("major", "custom", etc.)
s.intervals()ArrayInterval array in semitones
s.period()NumberRepeat period in semitones (12 for a standard octave)
s.length()NumberNumber of scale degrees
s.mode(n)ScaleRotate to the nth mode (1-indexed)
s.contains(n)BooleanDoes the scale contain this interval?

Resonon ships with 14 built-in scale types. Different scales have different lengths.

ScaleAliasesNotesIntervals (semitones)Character
"major""ionian"70 2 4 5 7 9 11Bright, happy, resolved
"minor""aeolian"70 2 3 5 7 8 10Dark, melancholic
"dorian"70 2 3 5 7 9 10Minor with a bright 6th
"phrygian"70 1 3 5 7 8 10Spanish, exotic
"lydian"70 2 4 6 7 9 11Dreamy, floating
"mixolydian"70 2 4 5 7 9 10Bluesy major, rock
"locrian"70 1 3 5 6 8 10Unstable, diminished
"harmonic_minor"70 2 3 5 7 8 11Classical tension
"melodic_minor"70 2 3 5 7 9 11Jazz minor
"pentatonic_major"50 2 4 7 9Open, folk
"pentatonic_minor"50 3 5 7 10Blues, rock riffs
"blues"60 3 5 6 7 10Gritty, soulful
"whole_tone"60 2 4 6 8 10Dreamlike, ambiguous
"chromatic"120 1 2 3 4 5 6 7 8 9 10 11All semitones

scale_names() returns an array of all available scale name strings:

PRINT scale_names();
// ["major", "minor", "dorian", "phrygian", "lydian", "mixolydian",
// "locrian", "harmonic_minor", "melodic_minor", "pentatonic_major",
// "pentatonic_minor", "blues", "whole_tone", "chromatic"]

The seven diatonic modes are available both by name and by rotating a scale with mode():

let dorian = Scale("dorian");
let also_dorian = Scale("major").mode(2); // major mode 2 = dorian
PRINT dorian.intervals(); // [0, 2, 3, 5, 7, 9, 10]
PRINT also_dorian.intervals(); // [0, 2, 3, 5, 7, 9, 10]

load_scala(name) loads a scale from a Scala .scl tuning file. Scala files define scales using cents or ratios and support non-octave periods (e.g., the Bohlen-Pierce tritave).

let just = load_scala("just/5-limit");
let bp = load_scala("bohlen-pierce");

Search order:

  1. Relative to the current .non file
  2. ~/.resonon/scales/
  3. Bundled scales

The .scl extension is optional. The loaded result is a Scale with the correct period and fractional-semitone intervals.

A Key anchors a Scale at a root note. Build one from a root plus a scale name or Scale value:

let k = Key(C4, "major");
let micro = Key(D4, Scale(#[0, 2, 3.5, 5, 7, 8.5, 10]));

A Key exposes its parts as properties and its operations as methods:

MemberReturnsDescription
k.scaleScaleThe underlying (rootless) scale
k.rootNoteThe root note
k.nameStringThe scale’s name
k.degree(n)NoteNote at the nth degree (1-indexed; wraps; ≤0 goes below root)
k.notes()ArrayOne octave of the scale as rooted Notes
k.chord(deg [, quality] [, inv])ArrayDiatonic triad (or explicit quality) on a degree
k.chord7(deg [, inv])ArrayDiatonic 7th chord on a degree
k.quantize(note)NoteSnap a note to the nearest scale tone
k.transpose(semitones)KeyNew key with the root shifted
let k = Key(C4, "major");
PRINT k.notes(); // [C4, D4, E4, F4, G4, A4, B4]
PRINT k.degree(1); // C4 (root)
PRINT k.degree(5); // G4 (fifth)
PRINT k.degree(8); // C5 (wraps an octave)
PRINT k.degree(0); // B3 (one step below the root)
PRINT k.chord(2); // [D4, F4, A4] (diatonic ii triad)
PRINT k.chord7(5); // [G4, B4, D5, F5] (diatonic V7)
PRINT k.quantize(C#4); // C4 (snaps into the scale)

Inside patterns, write scale degrees with a ^ prefix instead of absolute notes. Degrees stay abstract until they are resolved against a key.

[^1 ^3 ^5 ^8] // degrees
[^1: ^4: ^5:] // diatonic chords on degrees (trailing :)
[^1:min7] // explicit chord quality on a degree

Resolve them in one of two ways.

Per pattern with .in_key(key):

let melody = [^1 ^3 ^5 ^-2].in_key(Key(C4, "major"));
let chords = [^1: ^4: ^5:].in_key(Key(A3, "minor"));

Per track with track.key(...) — available on both MidiTrack and AudioTrack. Degrees on the track resolve automatically, and absolute notes are quantized to the scale:

let lead = MidiTrack(1, 100, NUL, "lead");
lead.key(Key(C4, "minor")); // or lead.key("minor", C4)
lead << [^1 ^3 ^5];

Microtonal keys carry through this path: fractional degrees become fractional MIDI pitches, played via pitch bend (use track.tuning() or track.mpe() on the track).

chord(root, quality, inversion?) builds a chord from any root, independent of a key:

PRINT chord(C4, "major"); // [C4, E4, G4]
PRINT chord(C4, "min7"); // [C4, Eb4, G4, Bb4]

Resonon ships with 18 built-in chord qualities.

QualityAliasesNotesIntervals (semitones)Character
"major""maj"30 4 7Bright, stable
"minor""min"30 3 7Dark, melancholic
"dim"30 3 6Tense, unstable
"aug"30 4 8Bright, unsettled
"7""dom7"40 4 7 10Bluesy, wants to resolve
"maj7"40 4 7 11Lush, jazzy
"min7"40 3 7 10Smooth, mellow
"dim7"40 3 6 9Dramatic, symmetrical
"aug7"40 4 8 10Altered dominant
"sus2"30 2 7Open, ambiguous
"sus4"30 5 7Suspended, tense
"6"40 4 7 9Warm, vintage
"min6"40 3 7 9Bittersweet
"9"50 4 7 10 14Rich dominant
"maj9"50 4 7 11 14Lush, expansive
"min9"50 3 7 10 14Smooth, complex
"add9"40 4 7 14Bright, open
"power""5"20 7Neutral, heavy

chord_names() returns all available chord qualities.

Within patterns, chords can be written directly using compact notation:

Root:Quality~Inversion
// These are equivalent:
let a = [C4:maj E4:min A3:min7];
let b = Sequence(Stack(chord(C4, "major")), Stack(chord(E4, "minor")), Stack(chord(A3, "min7")));

The optional third argument to chord() (or ~n in inline notation) rearranges chord tones across octaves. Positive inversions raise the lowest notes; negative ones drop the highest.

PRINT chord(C4, "major"); // [C4, E4, G4]
PRINT chord(C4, "major", 1); // [E4, G4, C5]
PRINT chord(C4, "major", 2); // [G4, C5, E5]
PRINT chord(C4, "major", -1); // [G3, C4, E4]

Use Stack() to play all chord notes at once. Use <> to alternate chords each cycle:

// Simultaneous
Stack(chord(C4, "major"));
// Progression — alternate a chord each cycle with inline notation
let progression = <C4:maj F4:maj G4:maj>;

interval(note, name) returns a single note offset from the given note by the named interval:

PRINT interval(C4, "P5"); // G4
PRINT interval(C4, "m3"); // Eb4
PRINT interval(C4, "M3"); // E4
PRINT interval(C4, "P8"); // C5
NameAliasesIntervalSemitones
"m2"Minor 2nd1
"M2"Major 2nd2
"m3"Minor 3rd3
"M3"Major 3rd4
"P4"Perfect 4th5
"tritone""TT"Tritone6
"P5"Perfect 5th7
"m6"Minor 6th8
"M6"Major 6th9
"m7"Minor 7th10
"M7"Major 7th11
"P8""oct"Octave12

Collect interval() results into an array to construct scales or chords not in the built-in library:

let root = C4;
let dim_scale = #[
root,
interval(root, "M2"),
interval(root, "m3"),
interval(root, "P4"),
interval(root, "tritone"),
interval(root, "m6"),
interval(root, "M6"),
interval(root, "M7")
];

That completes the language fundamentals. Next, put notes in motion by sequencing them with patterns.