Scales, Keys, and Modes

In the previous tutorials we introduced melodies and harmonies. The rendered pitch is not determined only by harmony + degree. An important role plays the musical key and its mode. This tutorial shows how to deal with the key and mode attributes and how to define own scales.

Table of Contents

Major keys

Let us start with a very simple example of a scale.

function Main() {
  @span = 5;
  @Split(8).Then(idx => @degree = idx);
}
5.60 s

Each entity has its own key attribute, so changing the key anytime is straightforward. It defines the tonic of the current key as a chromatic offset from C. So key = 0 corresponds to C major, key = 1 corresponds to D major, key = 2 corresponds to D major, etc. (assuming we did not touch the default mode yet, hence the major mode). The following example plays all the 12 major keys.

function Main() {
  @span = 5 * 12;
  @Split(12).Then(k => {
    @key = k;
    @Split(8).Then(d => @degree = d);
  });
}
60.60 s

Based on what you have learned about chords in the tutorial on harmonies, you can try to change the previous example to play triads or other chords instead of single notes.

Modes

The keys are by default major (ionian). Other modes introduce a different sequence of semitones that can help to enrich your compositions. The second most prominent mode is of course the minor mode (aeolian). Just like keys, harmonies and degrees, mode is also a native entity attribute. By default, mode == 0, or using a different notation, mode == Mode.Ionian. In order to switch to a minor key, we need to take Mode.Aeolian instead.

function Main() {
  @span = 5;
  @mode = Mode.Aeolian;
  @Split(8).Then(idx => @degree = idx);
}
5.60 s

Important: With constants like Mode.Aeolian you can directly set the absolute mode value. D also offers relative changes. These are not ordered in the same way as it is common in basic music theory: ionian, dorian, phrygian, ... The ordering goes from dark to bright, adding a sharp sign towards bright and a flat sign towards dark. These steps correspond to intervals of fourths:

  • next brighter: ionian (I) → lydian (IV)

  • next darker: ionian (I) → mixolydian (-IV i.e. V)

The full progression from the darkest to the brightest goes as:

  • locrian (VII) → phrygian (III) → aeolian (VI) → dorian (II) → mixolydian (V) → ionian (I) → lydian (IV).

function Main() {
  @span = 5 * 7;
  @mode = Mode.Locrian;
  @Split(7).Then(idx => {
    @mode += idx;
    @Split(8).Then(d => @degree = d);
  });
}
35.60 s

Edge cases when mode would overflow are handled by increasing or decreasing the key as well:

  • next brighter: C lydian → C# locrian

  • next darker: C# locrian → C lydian

This way, relative mode changes are consistent in both notation and musical color.

function Main() {
  @span = 5 * 7;
  //by default we are in C
  @mode = Mode.Ionian;
  @velocity = 0.5;
  @Split(7).Then(idx => {
    if (idx == 2) //ionian at 0, lydian at 1, 2 loops to locrian but in a key a semitone higher
      @velocity = 0.9; //emphasize the locrian a semitone higher
    @mode += idx;
    @Split(8).Then(d => @degree = d);
  });
}
35.60 s

Let us enrich the melody example from the melody tutorial by a little bit of modal variation. Note the dedicated Rnd.Mode random generator that returns a Mode from the given range.

var melodyQ = [ 0, 1, 2, 3, 3, 4, 3, 1, 2, 2 ];
var melodyA = [ 0, 2, 0, 1, 1, 2, 1, 2, 0, 0 ];
var durations = [ 1f, 1f, 1f, 2f, 2f, 1f, 1f, 1f, 2f, 2f ];

function Main() {
  @span = 1.5 * (melodyQ.Length + melodyA.Length);
  @mode = @Rnd.Mode(Mode.Aeolian, Mode.Ionian); //random base mode
  @Split(
      () => @Split(2).Then(idx => @Sentence(idx == 0 ? melodyQ : melodyA)),
      () => {
        @mode += @Rnd.Choice(-2, -1, 1, 2); //subtle modal change
        @Split(2).Then(idx => @Sentence(idx == 0 ? melodyQ : melodyA));
      }
  );
}

function Sentence(int[] melody) =>
  @Split(durations).Then(idx => {
    @degree = melody[idx];
    //shorten at the phrase end to allow for breating
    if (idx % 5 == 4)
      @span = Math.Max(@span / 2, @span - 1); //try shortening by half but at most by 1s
});
30.06 s

Accidentals

When changing between modes, sharps and flats are added following the circle of fourths. E.g., after adding a ♭ on D, the next lower mode will add a ♭ to G. Such build-up limits the combinations considerably.

It is also possible to add accidentals that are out of any key-mode combination. The alter attribute adds a chromatic offset to an entity. So the final formula for the rendered pitch is: octave * 12 + key + harmony(mode) + degree(mode) + alter.

The next example converts the first D into a D and G into G♯.

function Main() {
  var melody = [ 0, 1, 2, 3, 3, 4, 3, 1, 0, 0 ];
  var alters = [ 0, -1, 0, 0, 0, 1, 0, 0, 0, 0 ];
  var durations = [ 1f, 1f, 1f, 2f, 2f, 1f, 1f, 1f, 2f, 2f ];
  @span = melody.Length;
  @Split(durations).Then(idx => {
    @degree = melody[idx];
    @alter = alters[idx];
  });
}
10.60 s

Scales

Up to now all examples used the default diatonic scale and its modes. It consists of seven of the twelve pitch classes. That seven tones are distributed rather equally. In the major mode the set is 0,2,4,5,7,9,11 which corresponds to C,D,E,F,G,A,B.

If expressed as a sequence of interval differences, this is equivalent to 2-2-1-2-2-2-1. With seven pitch classes there are seven modes. Each mode is just a rotation of the differences sequence. For example rotating one to the right yields 1-2-2-1-2-2-2 which corresponds to 0,1,3,5,6,8,10 or C,:dflat:,E♭,F,G♭,A♭,B♭ being the locrian mode (in D denoted as -5). Modes add accidentals in a strict order (circle of fourths). With just 7 rotation options they cover just a little portion of possibilities.

Defining a custom scale allows to overcome that limitation. The following examples show how to define the two alternate minor scales: harmonic minor and melodic minor scales.

function Main() {
  var natural = [0,2,3,5,7,8,10]; //diffs 2-1-2-2-2-1-2  same as Mode.Aeolian
  var harmonic = [0,2,3,5,7,8,11]; //diffs 2-1-2-2-1-3-1 an augmented interval before the final scale degree
  var melodic = [0,2,3,5,7,9,11]; //diffs 2-1-2-2-2-2-1 a different grouping of half and whole tones

  var minorScales = [natural, harmonic, melodic];
  @span = minorScales.Length * 3;

  @Split(minorScales.Length).Then(i => @scale = minorScales[i]);
  @Split(8).Then(j => @degree = j);
}
9.60 s
Your turn

Jazz minor scale

Create the jazz minor scale. Derived from the major/ionian mode, it has a minor third.

A little practice, a little music.

Edit the starter, run your code, and see how it sounds.

Scales are not limited to seven pitch classes. They may contain fewer or more. Let us try out a hexatonic augmented scale.

function Main() {
  @span = 3;
  @scale = [0, 3, 4, 7, 8, 11]; //hexatonic C E♭ E G G♯ B (diffs 3-1-3-1-3-1)
  @Split(@scale.Count + 1).Then(j => @degree = j);
}
3.60 s
function Main() {
  @span = 16;
  @key += 2;
  @scale = [0, 3, 4, 7, 8, 11]; //hexatonic D F F♯ A A♯ C♯
  @octave -= 1;

  @Split(FILL(1).Then(A), FILL(1).Then(B));
  @Split(
      FILL(2).Then(i => @octave -= 1),
      FILL(@span < 1 ? 1 : 2).Then(i => @chord += @harmony % 2 == 0 ? [1,3] : [2,4])
  );
}

var motiveA = [2,1,2,1,0];
var rhythmA = [2,2,1,1,2];
function A() {
  @Split(2).Then(part =>
    @Split(rhythmA).Then(i => @harmony = motiveA[i] + (part.Max - part) * 3 + 1)
  );
}

var motiveB = [5,3,0];
var rhythmB = [2,2,4];
function B() {
  @Split(2).Then(part => {
    if (part == 0)
        @Split(8).Then(i => @harmony = i);
    else
        @Split(rhythmB).Then(i => @harmony = motiveB[i]);
  });
}
16.60 s

Scales are implemented similar to chords, they are sets. So it is possible to add or remove pitch classes by one or combine scales.

function Main() {
  @span = 3;
  @scale = [0, 4, 8]; //augmented triad C E G♯
  @scale += [3, 7, 11]; //adds another augmented triad
  //or even better
  //@scale += @scale >> 3; //adds the same triad but shifted 3 semitones higher
  @Split(@scale.Count + 1).Then(j => @degree = j);
}
3.60 s

Try out to change the shift of the second chord that builds the scale. Shifting a scale moves its tones during the script evaluation so that the scale root may be larger than 0. But at the end when the exact tones are determined, all scales are rotated back so that the first tone is always at 0. In order to apply a permanent shift, use key instead.

Also feel free to experiment with other scales like pentatonic or octatonic.

Since modes only rotate the sequence of differences, they apply to arbitrary scales. The naming is, however, not valid anymore. The ordering of modes also does not follow the circle of fourths. It rather tries to order them ascending by minimal change so that the perception from darkest to the lightest stays. For sparse scales this is not recognizable anymore.

Many scales do not contain less modes than expected if they exhibit repeating patterns in the difference sequence rotations. The augmented scale is one of them. Instead of the maximum of 6 modes, it only offers 2 distinct modes: 3-1-3-1-3-1 and 1-3-1-3-1-3

function Main() {
  @span = 3;
  @scale = [0, 3, 4, 7, 8, 11]; //hexatonic C E♭ E G G♯ B
  @span = 3 * @scale.Modes;
  @mode -= @scale.ModalShift; //the darkest mode
  @Split(@scale.Modes).Then(idx => {
    @mode += idx;
    @Split(@scale.Count).Then(d => @degree = d);
  });
}
6.60 s

Chromatic pitch and non-diatonic chords

Basic structures of D are built around the traditional Western tonal system where harmonic functions decide about the quality of a chord. By default, D interprets harmony, degree, and chord relative to the entity's current scale. For example, Chord.Triad selects the scale degrees [0, 2, 4]. The resulting intervals therefore depend on the scale, mode, and harmonic root.

Even with modulations, an augmented triad chord cannot be created in a standard diatonic setting. When the desired chord quality is not available from the current scale, one option is to slice the chord into individual notes and alter them separately. The following example changes a major triad into an augmented triad by raising its fifth. The alter attribute applies a chromatic shift to the entity.

function Main() {
  @chord += [2,4];
  @Slice().Then(i => {
    if (i.IsLast) @alter++;
  });
}
1.60 s

Note that alter and key can both shift the entity chromatically, so technically the perform the same operation, but they carry different semantic meaning. Borrowing a pitch not present in the current scale does not necessarily imply changing the key.

Another option is the Chromatic command. It converts the entity's pitch data to the twelve-note chromatic scale. Chord offsets can then be expressed directly in semitones, so [0, 4, 8] represents an augmented triad.

Chromatic pitches and chords are frequently used in tonal music; atonality describes music that does not establish a governing tonal center. Chromatic can therefore be used for either tonal chromatic harmony or atonal material.

function Main() {
  @Chromatic(Triad.Augmented);
  //same as @Chromatic([0, 4, 8]);

  //the current entities now use the chromatic scale
  //so for example adding the octave will require a + [12]
  @span *= 2;
  @Split(
    () => {},
    () => @chord += [12]
  );
}
2.60 s

Non-standard scales, transitions between keys and modal changes form a large block of songwriting theory. Both are closely related to harmonies and will be further explored in more advanced tutorials.

The next tutorial introduces dynamics and smooth transitions like crescendo. We already introduced it partially with the velocity attribute.

Search for something