Attribute Extraction

The previous tutorial showed how the whole scope can be extracted to a variable. It is also possible to selectively extract a single attribute. However, it may be slightly more complicated.

Table of Contents

Attribute of a single entity

As long as there are no structural commands (those producing more than a single entity) preceding the extraction operation, it is possible to extract attribute values directly by a simple assignment.

function Main() {
  @degree = 4; //G4;
  var tmp = @degree; //extract the value
  @Axiom(); //replace the current scope with a new axiom
  @degree = tmp; //restore the value
  return @Axiom() + @Scope(); //Axiom() creates C4, Scope() should contain the restored G4
}
2.60 s

Attempted attribute extraction from a scope with more than one entity will throw an error.

function Main() {
  @degree = 4; //G4;
  @Split(2);
  var tmp = @degree; //error here
}
  • Implicit entity extraction failed: More than a single entity present in the current working set. Count = 2.Line: 4

But callbacks actually start with a single entity. The following example extracts the randomly generated sequence and adds passing notes in both hands. Note how if partitions the scope set into two using the previously assigned color as a discriminator for voices. Scope then exposes the working set of the respective branch for indexed processing.

var count = 16;
function Main() {
  @span = count;

  var sequence = new List<int>();
  @Split(count).Then(i => {
    @degree = i < i.Max ? @Rnd.Int(-2, 3) : 0; //random sequence
    sequence.Add(@degree); //Then is evaluated per entity, so attribute extraction works here
  });

  @Clone().Then(() => {--@octave; @color = "orange"; }); //orange will be low voice

  if (@color != "orange") //high voice
    @Scope().Then(i => { //iterate all entities in the scope
      var diff = i < i.Max ? sequence[i + 1] - sequence[i] : 0; //get the diff between each pair
      var absDiff = Math.Abs(diff);
      if (absDiff > 1)
          @Split(absDiff).Then(j => @degree += (diff > 0 ? j : -j)); //smooth all leaps
    });
  else
    @Scope().Then(i => { //low voice
      if (@degree <= 0) //add a bit of harmonic intervals
          @harmony = @Rnd.Choice(0, 2);
      var diff = i < i.Max ? sequence[i + 1] - sequence[i] : 0; //same diff as above
      var absDiff = Math.Abs(diff);
      if (absDiff <= 1 && i < i.Max && @Rnd.Bool()) //complementary activation to the high voice, but sparser due to the random bool
          @Split(@Rnd.Int(2..4)).Then(j => @degree += (j % 2) == 1 ? @Rnd.Choice(-3, -2, -1, 1, 2) : 0); //random rhythm and melody
    });
}
16.60 s

The previous example demonstrated the usage of Scope to index the scope. Alternatively, the index may be stored in an attribute, resulting in a simpler program.

var count = 32;
function Main() {
  @span = count;

  var sequence = new List<int>();
  @Split(count).Then(i => {
    @index = i;
    @degree = i < i.Max ? @Rnd.Int(-2, 3) : 0;
    sequence.Add(@degree);
  });

  @diff = @index < @index.Max ? sequence[@index + 1] - sequence[@index] : 0;

  @Clone().Then(() => {--@octave; @color = "orange"; });
  if (@color != "orange") {
    if (Math.Abs(@diff) > 1)
        @Split(Math.Abs(@diff)).Then(i => @degree += (@diff > 0 ? i : -i));
  } else {
    if (@degree <= 0)
      @harmony = @Rnd.Choice(0, 2);
    if (Math.Abs(@diff) <= 1 && @index < @index.Max && @Rnd.Bool())
      @Split(@Rnd.Int(2..4)).Then(i => @degree += (i % 2) == 1 ? @Rnd.Choice(-3, -2, -1, 1, 2) : 0);
  }
}
32.60 s

Select

Attribute extraction by assignment works only for scopes with a single entity. If there are several entities in the scope, e.g. after a Split, an error is thrown. The solution in such situation is the Select command. It takes a custom extractor function and applies it to each entity separately. The results are collected in an array of the respective type.

var count = 4;
function Main() {
  @Split(count).Then(i => @degree = i);
  //var degree = @degree; // this would throw an error
  var degrees = @Select(() => @degree); //extracts sbyte[]{0, 1, 2, 3}
  @Axiom(); //replace the current working set with a new axiom
  @Split(degrees.Length).Then(i => @degree = degrees[i]); //restore the values
}
1.60 s

Keep in mind that the ordering of the values in the array depends on the execution order of previous commands. In complex situations, it is not advised to rely on a particular order. A further limitation is that the custom extractor must not include structural command which would create new entities. It would result in the same exception as observed for assignment extraction.

If no explicit return is specified in the extractor, the default return is used as a fallback. In D that is the scope. If a scope is returned, it is considered a single return value, so in fact it can feature several entities, being an exception to the previously discussed limitation.

var count = 4;
function Main() {
  @Split(count).Then(i => @degree = i * 2);
  var scopes = @Select(() => {}); //extract all Music objects, each contains a single entity
  @Axiom(); //replace the current working set with a new axiom
  #Overlay(scopes); //plays all entities at once
}
0.85 s

The following example shows how the attribute extractor can be used to add passing notes to a very random melody.

function Main()
{
  @seed = 42;
  @span = 18;
  //2 phrases, each 8 beats
  @Split(16).Then(i => {
    @index = i;
    if (!i.IsLast)
      @degree = @Rnd.Int(-4..5); //F3 to A4
    //else ending at tonic
  });

  var melody = @Select(() => @degree);
  if (@index % 2 == 0) //every even beat, meaning that it will not hit the last one
      @Split(@Rnd.Int(
                    Math.Clamp(Math.Abs(@degree - melody[@index + 1]) - 2, 1, 4),
                    Math.Clamp(Math.Abs(@degree - melody[@index + 1]), 1, 4))
      ).Then(i => {
        //if (i > 0)
            //@degree = @Rnd.Int(melody[@index + 1] + 1, @degree - 1);
        var relative = i / (i.Max + 1f);
        @degree = (int)Math.Round(@degree * (1f - relative) + melody[@index + 1] * relative);
      });
}
18.60 s
Search for something