7.12.11. STRUDEL-11 — Scales & Music Theory
Writing melodies in absolute pitches (c4, e4, g4) gets old
fast — every key change means rewriting every note. Strudel scales let
you write in degrees (0, 1, 2, …) against a named scale. Change the
scale and the same degree pattern produces a new key, mode, or mood.
7.12.11.1. Part A: scale_pattern — degrees against a scale
scale_pattern(notation, scale_def, sound) is a shorthand for
n(notation) |> scale(scale_def) |> sound(sound). The same
"0 2 4 6" against C major and C minor is a one-line A/B test:
let pat <- stack([
scale_pattern("0 2 4 6", "C4:major") |> decay(0.2) |> gain(0.5),
scale_pattern("0 2 4 6", "C4:minor", "sawtooth") |> sustain(0.3) |> release(0.2) |> gain(0.3) |> lpf(2000.0)
])
Major sounds bright; minor sounds dark — same degrees, different intervals.
7.12.11.2. Part B: pentatonic — only “safe” notes
"C4:major:pentatonic" maps degrees 0..4 onto C, D, E, G, A — a
five-note scale with no semitone clashes, so any degree pattern always
sounds consonant. Useful when you want guaranteed-pretty arpeggios
without thinking about voice leading:
let pat <- scale_pattern("0 1 2 3 4", "C4:major:pentatonic") |> decay(0.1) |> release(0.2)
7.12.11.3. Part C: modes — same root, different intervals
Modes are seven-note scales that share the white-keys interval pattern but start on a different scale degree. Two of the most distinctive:
dorian— minor with a raised sixth. Jazzy, folksy.mixolydian— major with a flattened seventh. Bluesy.
let dorian_pat <- scale_pattern("0 1 2 3 4 5 6 7", "D4:dorian") |> decay(0.15) |> release(0.2)
let mixo_pat <- scale_pattern("0 1 2 3 4 5 6 7", "G4:mixolydian") |> decay(0.15) |> release(0.2)
The other supported modes are phrygian, lydian and locrian;
all share the seven-note interval shape but rotate the starting degree.
7.12.11.4. Part D: transposition
Two ways to transpose:
Change the scale root.
"C4:major" → "E4:major"— degrees stay the same; pitches shift.Add ``transpose(semitones)``. Applied after pitch resolution, shifts every note by the given number of semitones.
let pat <- stack([
scale_pattern("0 2 4 2", "C4:major") |> decay(0.15) |> gain(0.5),
scale_pattern("0 2 4 2", "C4:major") |> transpose(7.0) |> decay(0.15) |> gain(0.5)
])
Above, the second voice plays the same melody a perfect fifth higher.
7.12.11.5. Part E: two octaves, same scale
Stack two scale_pattern voices at different octaves to build a
bass + lead pair from one scale — switch the scale root (C4 vs
C5) to move between octaves while keeping the degree pattern:
let pat <- stack([
scale_pattern("0 2 4 3", "C4:major:pentatonic", "sawtooth") |> sustain(0.3) |> release(0.1) |> lpf(500.0) |> gain(0.4),
scale_pattern("4 2 3 0", "C5:major:pentatonic") |> decay(0.05) |> release(0.2) |> delay(0.3) |> delayfeedback(0.3) |> gain(0.5)
])
Same scale, two octaves, two different envelopes — instant counterpoint.
7.12.11.6. Part F: degree_to_note — the primitive under scale
scale is built on degree_to_note(degree, root_midi, intervals),
which converts a single scale degree to a MIDI note. Degree 0 is the
root, degree 7 wraps up an octave, and negative degrees wrap backwards.
get_scale_intervals_by_name returns the semitone table for a named
scale, so you can resolve degrees by hand and feed the MIDI numbers
straight into note():
let root = 60 // C4
let intervals <- get_scale_intervals_by_name("major")
for (deg in range(0, 8)) {
let midi = degree_to_note(deg, root, intervals)
print(" degree {deg} -> MIDI {int(midi)}\n")
}
This is the same mapping n("0 2 4") |> scale("C4:major") performs
internally — reaching for it directly is useful when you need the MIDI
numbers themselves rather than a finished pattern.
7.12.11.7. Part G: add — shift notes by semitones
add(pat, n) adds n semitones to every event’s note — the
chromatic-shift alias of transpose. run(8) produces a 0..7 ramp
across the cycle, and add(48) lifts it into the audible C3..G3 range
as a rising chromatic line:
let pat <- run(8) |> add(48.0) |> sound("sine") |> sustain(0.3)
7.12.11.8. Part H: freq — pitch directly in Hz
note() derives the playback frequency from a MIDI number via
note_to_freq. freq() bypasses that path and sets an absolute
frequency in Hz, so you can use tunings or raw frequency sweeps that
have no MIDI name. Here a sawtooth steps through 220 / 277 / 330 / 440
Hz (an A-major-ish chord):
let pat <- (
s("sawtooth*4")
|> freq(note("220 277 330 440"))
|> lpf(2500.0) |> release(0.2) |> gain(0.5)
)
See also
Full source: tutorials/daStrudel/daStrudel_11_scales_music_theory.das
Previous tutorial: STRUDEL-10 — ADSR & Envelope Shaping
Next tutorial: STRUDEL-12 — Synthesis