Melody

In the previous tutorials we introduced attributes and batch functions. We should have all the tools necessary to start creating some interesting stuff. Let us create a simple melody first. We will later develop it further.

Table of Contents

Raw melody

Imagine you are in C major key. The basic melody goes like this: C D E F F _ G F E C C. Since D operates in diatonic mode by default (i.e. degree attribute relates to the degrees of the current scale), we will encode the melody into an array of relative diatonic distances to the tonic: 0 1 2 3 3 _ 4 3 2 0 0.

Let us translate this into D code. We store the relative distances in an array. Setting the span (in seconds) to equal the array length should be fine for now. We will use the Split function to divide the initial entity into as many new entities as we defined in the melody. Remember that Split orders them linearly, so they will sound one after each other in regular intervals. Finally, using the split indexer we assign the degree of each new entity to the corresponding melody item.

function Main() {
  var melody = [ 0, 1, 2, 3, 3, 4, 3, 1, 0, 0 ]; //using a collection expression (new in C# 12)
  @span = melody.Length;
  @Split(melody.Length).Then(idx => @degree = melody[idx.Index]);
}
10.60 s

Varying note durations

All notes in the melody are equally long. It would be better to close the short phrases by longer notes. Along with the melody array we can create a second array with the respective durations. Since we will use Split again, it will fill the available span. So it is sufficient that their relative ratios are correct, the exact scaling will be computed by Split in the background.

Let us make each pair of the F notes twice as long, so they will be weighted by 2f while others will get 1f. So the array will be 1f 1f 1f 2f 2f _ 1f 1f 1f 2f 2f. Relative durations may be floats but also integers.

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

This melody sounds like it is resolving too fast. What about producing a variation that would make it an open question and a concluded response. We will use a custom function Sentence for that. In the previous examples, variables were declared locally in Main. To access from the custom Sentence function, they need to be moved to the top level — outside of any function.

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 = [ 1, 1, 1, 2, 2, 1, 1, 1, 2, 2 ];

function Main() {
  @span = melodyQ.Length + melodyA.Length;
  @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 breathing
    if (idx % 5 == 4)
      @span = Math.Max(@span / 2, @span - 1); //try shortening by half but at most by 1s
});
19.89 s

A second voice would certainly make the melody more interesting. Voicing is part of later tutorials on harmony and orchestration. For now we can use Split to approach the effect by playing the inverted echo of the melody interleaved with the original. The inversion is achieved by negating the degree which creates a counter movement. This is not true polyphony, Split places the original and inverted versions of each note consecutively.

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 = [ 1, 1, 1, 2, 2, 1, 1, 1, 2, 2 ];

function Main() {
  @span = melodyQ.Length + melodyA.Length;
  @Split(2).Then(idx => @Sentence(idx == 0 ? melodyQ : melodyA));
}

function Sentence(int[] melody)
{
  @Split(durations).Then(i => @degree = melody[i]);
  //interleaving
  @Split(
      () => {}, //just a pass through of the original tone
      () => {   //inverted echo tone
        @octave -= 1;
        @degree *= -1; //invert the melody
  });
}
20.60 s

The example melodies we created were all constant without any variation. The next tutorial will introduce randomization of attributes like degree to produce a new melody each time the music script is executed.

D also has a specialized melody generator which is based on constraint programming. It will be covered by one of the advanced tutorials on constraint generation.

Search for something